
Broadsheet Card
- 1 installs
- 1 repo stars
- Updated June 28, 2026
- geekjourneyx/broadsheet-card
Creates validated six-page vintage-broadsheet editorial image cards from a topic or notes, exporting HTML and PNGs for social carousels and article images.
About
A skill that treats card creation as publishing, editing content into a CardPlan and ColorPlan then typesetting six broadsheet-style pages and rendering validated PNGs. A developer or creator uses it for WeChat article images, Xiaohongshu/Rednote carousels, or social graphics.
- CardPlan, three cover candidates, and ColorPlan required before HTML
- Validation scripts for card plan, layout, and contrast plus PNG rendering
Broadsheet Card by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,200 of 1,335 Generative Media skills by installs in the Skillselion catalog
- Data as of Jul 24, 2026 (Skillselion catalog sync)
npx skills add https://github.com/geekjourneyx/broadsheet-card --skill broadsheet-cardAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 1 |
| Last updated | June 28, 2026 |
| Repository | geekjourneyx/broadsheet-card ↗ |
What it does
Creates validated six-page vintage-broadsheet editorial image cards from a topic or notes, exporting HTML and PNGs for social carousels and article images.
Files
Broadsheet Card
Create a six-page Vintage Broadsheet editorial card set. Treat the job as publishing, not decoration: edit the content first, then typeset it, render PNGs, and validate the output. The cards can be used as WeChat article images, Xiaohongshu/Rednote carousels, social graphics, or standalone publication-style visuals.
Default Output
- Canvas: 1080 x 1440 px
- Ratio: 3:4
- Pages: 6 unless the user explicitly asks otherwise
- Format: HTML plus PNG exports
- Files:
index.htmlmanifest.jsonoutput/page-01-cover.pngthroughoutput/page-06-checklist.png
Required Workflow
1. Parse the user input into a ContentBrief. 2. Read references/copywriting.md and create a CardPlan. 3. Read references/cover-system.md; generate three cover candidates and select one. 4. Read references/color.md; create a ColorPlan. 5. 🔴 CHECKPOINT: Do not write HTML until CardPlan, cover selection, and ColorPlan are coherent. 6. Read references/vintage-broadsheet-design.md. 7. Read references/layout-recipes.md. 8. Read references/html-contract.md. 9. Generate HTML using templates/base.html and templates/components.css as the structural model. 10. Run scripts/validate-card-plan.js. 11. Run scripts/validate-layout.js. 12. Run scripts/validate-contrast.js. 13. Fix failures by rewriting content or layout. Do not shrink text below the limits. 14. Run scripts/render.js to export PNGs. 15. Run scripts/make-thumbnail-preview.js when image tooling is available. 16. Review against references/quality-rubric.md. 17. Return the generated file list and a short note on the editorial structure.
Hard Rules
- Never generate HTML directly from the topic. Create a
CardPlanfirst. - Generate three cover candidates before choosing the final cover.
- Create a
ColorPlanand derive CSS tokens from it. - Each page must carry one main conclusion.
- Cover headline: 8-18 Chinese characters preferred.
- Body text: 28px or larger.
- Critical text must stay inside the 72px safe area.
- Red accent appears at most twice per page.
- Important text must not sit on noisy texture or image backgrounds.
- If content is too long, summarize it. Never shrink the font to fit.
- Avoid external fonts, CSS, scripts, or images unless the user provides trusted assets.
- If the user asks for multiple visual styles in one card set, narrow to Vintage Broadsheet or ask them to choose.
Default Page Sequence
1. Cover: strong hook 2. Problem: the mistake or tension 3. Framework: the core decision model 4. Detail: the most important mechanism 5. Comparison: wrong way vs right way 6. Checklist: save-worthy summary
When to Refuse or Ask for More Input
Ask for more content if the user provides only a vague theme and expects factual claims. Refuse or redirect requests that require sacrificing readability for heavier vintage effects, mixing many visual styles, or cramming a long article into one image.
Failure Handling
| Trigger | First repair | If still failing |
|---|---|---|
| Source content is vague or factual claims are unsupported | Ask for source text, audience, and intended use | Produce only a planning brief, not final cards |
| Topic is medical, legal, financial, or otherwise high-stakes | Ask for sources and narrow to educational framing | Refuse specific advice; offer a neutral explainer structure |
| CardPlan does not fit six pages | Rewrite page claims and shorten body copy | Ask user to split the topic into multiple card sets |
| Layout or contrast validation fails | Rewrite HTML/CSS using the same design tokens | Reduce content density; never reduce body text below 28px |
| Playwright render fails | Report the exact command and error | Deliver validated HTML plus next render command |
Anti-Patterns
Do not create:
- platform-locked output when the user asked for general article images or social cards
- vintage decoration without CardPlan, ColorPlan, and validation
- yellow parchment, scrapbook, wanted-poster, or generic newspaper-template visuals
- long article screenshots, tiny body text, or dense filler columns
- financial, medical, or legal recommendation cards without sources and careful framing
node_modules/
.repo.git/
.DS_Store
npm-debug.log*
# Generated sample exports
examples/*/output/*.png
examples/*/output/thumbnails/
# Local optimization / backup artifacts
darwin-results.tsv
SKILL.md.bak.*
# Generated README assets can be regenerated unless explicitly committed later
assets/*.tmp.*
interface:
display_name: "Broadsheet Card"
short_description: "Publish validated editorial image cards"
brand_color: "#9B1C16"
default_prompt: "Use $broadsheet-card to turn my topic and notes into a validated six-page Vintage Broadsheet card set."
policy:
allow_implicit_invocation: true
Changelog
v1.0.0 - 2026-06-02
- 首个正式版本发布。
- 新增 runtime-neutral 的 Agent Skill:
broadsheet-card。 - 支持把主题、大纲、文章或零散笔记转成经过验证的复古报刊风多页图文卡片。
- 建立
CardPlan、封面候选、ColorPlan、HTML-to-PNG、版式检查、对比度检查和 manifest 检查工作流。 - 内置 references、templates、scripts、eval prompts,以及
gold-ai-tool-guide黄金样例。
{
"skill_name": "broadsheet-card",
"evals": [
{
"id": 1,
"prompt": "把这段关于 AI 工具付费判断的长文做成 6 页出版感图文卡片,Vintage Broadsheet 风格,要有封面。",
"expected_output": "Generates a CardPlan, ColorPlan, self-contained HTML, six 1080x1440 PNG pages, a manifest, strong broadsheet styling, readable Chinese text, and passing layout/contrast checks.",
"files": []
},
{
"id": 2,
"prompt": "做一套 6 页公众号贴图:主题是如何判断一门副业课是不是割韭菜,风格像调查报道式老报纸。",
"expected_output": "Uses investigative broadsheet language, strong cover, split-verdict comparison page, save-worthy checklist, and avoids parchment/template styling.",
"files": []
},
{
"id": 3,
"prompt": "把一本商业书的核心笔记做成复古报刊感社交图文卡片,要求高级、克制、适合收藏。",
"expected_output": "Adapts the fixed six-page structure to book notes with lower-conflict editorial tone, clear hierarchy, and no overdecorated vintage effects.",
"files": []
}
]
}
{
"topic": "普通人如何判断一个 AI 工具值不值得付费",
"audience": "知识型图文读者",
"content_type": "knowledge-guide",
"style": "vintage-broadsheet",
"canvas": {
"width": 1080,
"height": 1440,
"ratio": "3:4"
},
"cover_hook": "别再为AI工具乱花钱",
"deck": "5 个问题,判断它值不值得月付",
"cover_candidates": [
{
"type": "front-page-banner",
"headline": "别再为AI工具乱花钱",
"deck": "5 个问题,判断它值不值得月付",
"why_it_works": "直接命中付费焦虑,适合权威指南型内容。"
},
{
"type": "investigative-special-report",
"headline": "AI订阅,先查这5点",
"deck": "别让宣传页替你做决定",
"why_it_works": "调查报道感更强,但标题不如最终方案自然。"
},
{
"type": "classified-notice",
"headline": "AI工具付费前须知",
"deck": "一张可收藏的月付检查清单",
"why_it_works": "实用性明确,但停止信号较弱。"
}
],
"selected_cover": "front-page-banner",
"selection_reason": "该方案最清楚表达错误行为和收益,30% 缩略图下仍能读出核心冲突。",
"pages": [
{
"page": 1,
"role": "cover",
"recipe": "front-page-banner",
"headline": "别再为AI工具乱花钱",
"deck": "5 个问题,判断它值不值得月付",
"main_point": "付费前先看工作流,而不是宣传页。"
},
{
"page": 2,
"role": "problem",
"recipe": "lead-story",
"headline": "先别看功能,先看频率",
"main_point": "低频场景撑不起长期订阅。"
},
{
"page": 3,
"role": "framework",
"recipe": "numbered-framework",
"headline": "五个问题,过滤冲动消费",
"main_point": "用高频、复用、节省、替代、退出成本判断。"
},
{
"page": 4,
"role": "detail",
"recipe": "feature-column",
"headline": "真正值钱的是进入工作流",
"main_point": "工具是否自然出现在每天的任务里,比模型能力更重要。"
},
{
"page": 5,
"role": "comparison",
"recipe": "split-verdict",
"headline": "功能多,不等于值得买",
"main_point": "宣传页展示能力,工作流验证价值。"
},
{
"page": 6,
"role": "checklist",
"recipe": "clipping-checklist",
"headline": "付费前,保存这张清单",
"main_point": "下次想订阅前逐条过一遍。"
}
]
}
{
"color_read": "authoritative archival editorial for AI tool payment decisions",
"temperature": "warm-neutral",
"contrast_strength": "high",
"accent_strategy": "single oxblood editorial accent",
"palette": {
"background": "#f3e8d2",
"surface": "#f8eedb",
"primary_text": "#17130d",
"secondary_text": "#2b251a",
"muted_text": "#5b4b34",
"rule": "#a88e63",
"accent": "#9b1c16",
"accent_dark": "#64110d"
},
"usage_rules": {
"cover": "accent only for the SAVE THIS stamp",
"inner_pages": "mostly ink and rule; red reserved for numbering or pull quote emphasis",
"final_page": "red used for checklist marker and save stamp"
},
"contrast_requirements": {
"body_text": ">= 4.5",
"large_text": ">= 3.0",
"decorative_text": "non-semantic only"
}
}
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=1080, initial-scale=1">
<title>别再为 AI 工具乱花钱</title>
<link rel="stylesheet" href="../../templates/components.css">
</head>
<body>
<script type="application/json" data-card-plan="true">
{
"cover_hook": "别再为AI工具乱花钱",
"selected_cover": "front-page-banner",
"pages": ["cover", "problem", "framework", "detail", "comparison", "checklist"]
}
</script>
<script type="application/json" data-color-plan="true">
{
"color_read": "authoritative archival editorial",
"accent_strategy": "single oxblood editorial accent"
}
</script>
<main id="deck">
<section class="page cover" id="page-1" data-page="1" data-role="cover">
<header class="masthead">
<div class="masthead__top">
<span>PRESSNOTE STUDIO</span>
<span>VOL. 01</span>
<span>REDNOTE EDITION</span>
</div>
<div class="masthead__name">SPECIAL REPORT</div>
<div class="rule-double"></div>
</header>
<h1 class="cover-title" data-critical="true" data-min-font="96" data-contrast-check="true" data-color="#17130d" data-bg="#f3e8d2">别再为AI工具乱花钱</h1>
<p class="deck" data-critical="true" data-min-font="36" data-contrast-check="true" data-color="#2b251a" data-bg="#f3e8d2">5 个问题,判断它值不值得月付</p>
<div class="stamp" data-accent="red">SAVE THIS</div>
<div class="teasers">
<div class="teaser">
<strong>工作流</strong>
<p class="caption">先看它是否进入你的高频任务。</p>
</div>
<div class="teaser">
<strong>替代成本</strong>
<p class="caption">能替代时间、人力或稳定流程才值钱。</p>
</div>
<div class="teaser">
<strong>付费清单</strong>
<p class="caption">最后一页可直接收藏复用。</p>
</div>
</div>
</section>
<section class="page inner" id="page-2" data-page="2" data-role="problem">
<div class="folio"><span>PAGE 02</span><span>THE COST QUESTION</span><span>AI TOOLS</span></div>
<h2 class="page-title" data-critical="true" data-min-font="64" data-contrast-check="true" data-color="#17130d" data-bg="#f3e8d2">先别看功能,先看频率</h2>
<div class="grid">
<div style="grid-column: span 8;">
<p class="lead" data-critical="true" data-min-font="28" data-contrast-check="true" data-color="#17130d" data-bg="#f3e8d2">很多 AI 工具不是没用,而是没有频繁到值得月付。</p>
<p class="body" data-critical="true" data-min-font="28" data-contrast-check="true" data-color="#2b251a" data-bg="#f3e8d2">一次演示很惊艳,不代表它能进入你的日常。真正要问的是:它是否每周都替你完成一个稳定任务?如果答案是否定,先别急着订阅。</p>
<aside class="pullquote" data-critical="true" data-min-font="28" data-contrast-check="true" data-color="#17130d" data-bg="#f3e8d2">低频场景,撑不起长期订阅。</aside>
</div>
<aside class="sidebar" style="grid-column: span 4;">
<p class="caption">EDITOR'S NOTE</p>
<p class="body" data-critical="true" data-min-font="28" data-contrast-check="true" data-color="#2b251a" data-bg="#f8eedb">先记录一周真实使用次数,再决定是否付费。</p>
</aside>
</div>
</section>
<section class="page inner" id="page-3" data-page="3" data-role="framework">
<div class="folio"><span>PAGE 03</span><span>PAYMENT FRAMEWORK</span><span>FIVE QUESTIONS</span></div>
<h2 class="page-title" data-critical="true" data-min-font="64" data-contrast-check="true" data-color="#17130d" data-bg="#f3e8d2">五个问题,过滤冲动消费</h2>
<div class="number-block">
<div class="number-block__num" data-accent="red">01</div>
<div class="number-block__title" data-critical="true" data-min-font="28">它解决的是高频问题吗?</div>
<p class="body" data-critical="true" data-min-font="28" data-contrast-check="true" data-color="#2b251a" data-bg="#f3e8d2">低频需求适合免费试用,不适合长期订阅。</p>
</div>
<div class="number-block">
<div class="number-block__num">02</div>
<div class="number-block__title" data-critical="true" data-min-font="28">它能被反复复用吗?</div>
<p class="body" data-critical="true" data-min-font="28" data-contrast-check="true" data-color="#2b251a" data-bg="#f3e8d2">一次性灵感不能证明工具价值,稳定复用才可以。</p>
</div>
<div class="number-block">
<div class="number-block__num">03</div>
<div class="number-block__title" data-critical="true" data-min-font="28">它替代了什么成本?</div>
<p class="body" data-critical="true" data-min-font="28" data-contrast-check="true" data-color="#2b251a" data-bg="#f3e8d2">时间、人力、外包、错误率,至少要替代一种。</p>
</div>
</section>
<section class="page inner" id="page-4" data-page="4" data-role="detail">
<div class="folio"><span>PAGE 04</span><span>WORKFLOW TEST</span><span>VALUE MECHANISM</span></div>
<h2 class="page-title" data-critical="true" data-min-font="64" data-contrast-check="true" data-color="#17130d" data-bg="#f3e8d2">真正值钱的是进入工作流</h2>
<div class="grid">
<div style="grid-column: span 7;">
<p class="lead" data-critical="true" data-min-font="28" data-contrast-check="true" data-color="#17130d" data-bg="#f3e8d2">工具的能力上限,不等于你的实际收益。</p>
<p class="body" data-critical="true" data-min-font="28" data-contrast-check="true" data-color="#2b251a" data-bg="#f3e8d2">如果你需要刻意找机会使用它,它还没有成为生产资料。值得付费的工具,会自然出现在你的写作、检索、分析、复盘或交付流程里。</p>
</div>
<aside class="sidebar" style="grid-column: span 5;">
<p class="caption">FIELD TEST</p>
<p class="body" data-critical="true" data-min-font="28" data-contrast-check="true" data-color="#2b251a" data-bg="#f8eedb">连续 7 天记录:它帮你完成了哪些原本必须手动做的任务?</p>
</aside>
</div>
<div class="verdict" data-critical="true" data-min-font="28" data-contrast-check="true" data-color="#17130d" data-bg="#f3e8d2">判断价值,不看宣传页,看真实工作流。</div>
</section>
<section class="page inner" id="page-5" data-page="5" data-role="comparison">
<div class="folio"><span>PAGE 05</span><span>SPLIT VERDICT</span><span>MYTH VS TRUTH</span></div>
<h2 class="page-title" data-critical="true" data-min-font="64" data-contrast-check="true" data-color="#17130d" data-bg="#f3e8d2">功能多,不等于值得买</h2>
<div class="split">
<div class="split__col">
<div class="split__label">DO NOT PAY FOR</div>
<p class="body" data-critical="true" data-min-font="28" data-contrast-check="true" data-color="#2b251a" data-bg="#f8eedb">演示很强,但你一周只用一次。</p>
<p class="body" data-critical="true" data-min-font="28" data-contrast-check="true" data-color="#2b251a" data-bg="#f8eedb">功能很多,但没有替代任何成本。</p>
<p class="body" data-critical="true" data-min-font="28" data-contrast-check="true" data-color="#2b251a" data-bg="#f8eedb">买完还要刻意提醒自己使用。</p>
</div>
<div class="split__col">
<div class="split__label">PAY FOR</div>
<p class="body" data-critical="true" data-min-font="28" data-contrast-check="true" data-color="#2b251a" data-bg="#f8eedb">每天都进入你的固定工作流。</p>
<p class="body" data-critical="true" data-min-font="28" data-contrast-check="true" data-color="#2b251a" data-bg="#f8eedb">稳定减少时间、错误或外包成本。</p>
<p class="body" data-critical="true" data-min-font="28" data-contrast-check="true" data-color="#2b251a" data-bg="#f8eedb">不用犹豫就知道下次还会打开。</p>
</div>
</div>
<div class="verdict" data-critical="true" data-min-font="28" data-contrast-check="true" data-color="#17130d" data-bg="#f3e8d2">宣传页展示能力,工作流验证价值。</div>
</section>
<section class="page inner" id="page-6" data-page="6" data-role="checklist">
<div class="folio"><span>PAGE 06</span><span>CLIPPING CHECKLIST</span><span>SAVE THIS</span></div>
<h2 class="page-title" data-critical="true" data-min-font="64" data-contrast-check="true" data-color="#17130d" data-bg="#f3e8d2">付费前,保存这张清单</h2>
<ul class="checklist">
<li data-critical="true" data-min-font="28"><span data-accent="red">01</span>这个需求是否每周出现至少三次?</li>
<li data-critical="true" data-min-font="28"><span>02</span>它是否替代了明确的人力或时间?</li>
<li data-critical="true" data-min-font="28"><span>03</span>不用它时,你是否会明显变慢?</li>
<li data-critical="true" data-min-font="28"><span>04</span>它的结果是否能稳定复用?</li>
<li data-critical="true" data-min-font="28"><span>05</span>取消订阅后,是否真的会影响交付?</li>
</ul>
<div class="stamp" style="margin-top: 46px;" data-accent="red">CLIP & SAVE</div>
</section>
</main>
</body>
</html>
Input
主题:普通人如何判断一个 AI 工具值不值得付费
核心观点:
- 不要看工具宣传得多强,要看它是否进入你的高频工作流。
- 真正值得付费的工具,要么节省可感知时间,要么替代稳定成本,要么带来更好的输出质量。
- 功能多不等于值得买。一次性新鲜感不能支撑月付。
- 付费前先用 5 个问题过滤:高频吗、可复用吗、省时间吗、替代谁、退出成本高吗。
- 如果一个工具需要你刻意找场景使用,它大概率还不值得付费。
{
"format": "broadsheet-card-set",
"size": "1080x1440",
"ratio": "3:4",
"style": "vintage-broadsheet",
"card_plan": "card-plan.json",
"color_plan": "color-plan.json",
"files": [
"output/page-01-cover.png",
"output/page-02-problem.png",
"output/page-03-framework.png",
"output/page-04-detail.png",
"output/page-05-comparison.png",
"output/page-06-checklist.png"
],
"cover_title": "别再为AI工具乱花钱",
"recommended_caption": "保存这 5 个问题,下次给 AI 工具付费前先过一遍。",
"keywords": ["AI工具", "效率工具", "图文卡片", "付费指南"]
}
MIT License
Copyright (c) 2026 geekjourneyx
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.
{
"name": "broadsheet-card",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "broadsheet-card",
"version": "1.0.0",
"devDependencies": {
"playwright": "^1.54.0",
"sharp": "^0.34.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@img/colour": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.2.4"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
"cpu": [
"x64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
"cpu": [
"arm"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-ppc64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
"integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-riscv64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
"integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
"cpu": [
"riscv64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
"integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
"cpu": [
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
"cpu": [
"arm"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.2.4"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-ppc64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
"integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-riscv64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
"integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
"cpu": [
"riscv64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-riscv64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
"integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
"cpu": [
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.2.4"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.2.4"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.2.4"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
"integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
"cpu": [
"wasm32"
],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
"@emnapi/runtime": "^1.7.0"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
"integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
"integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/playwright": {
"version": "1.60.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz",
"integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.60.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.60.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz",
"integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/semver": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/sharp": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@img/colour": "^1.0.0",
"detect-libc": "^2.1.2",
"semver": "^7.7.3"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.34.5",
"@img/sharp-darwin-x64": "0.34.5",
"@img/sharp-libvips-darwin-arm64": "1.2.4",
"@img/sharp-libvips-darwin-x64": "1.2.4",
"@img/sharp-libvips-linux-arm": "1.2.4",
"@img/sharp-libvips-linux-arm64": "1.2.4",
"@img/sharp-libvips-linux-ppc64": "1.2.4",
"@img/sharp-libvips-linux-riscv64": "1.2.4",
"@img/sharp-libvips-linux-s390x": "1.2.4",
"@img/sharp-libvips-linux-x64": "1.2.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
"@img/sharp-libvips-linuxmusl-x64": "1.2.4",
"@img/sharp-linux-arm": "0.34.5",
"@img/sharp-linux-arm64": "0.34.5",
"@img/sharp-linux-ppc64": "0.34.5",
"@img/sharp-linux-riscv64": "0.34.5",
"@img/sharp-linux-s390x": "0.34.5",
"@img/sharp-linux-x64": "0.34.5",
"@img/sharp-linuxmusl-arm64": "0.34.5",
"@img/sharp-linuxmusl-x64": "0.34.5",
"@img/sharp-wasm32": "0.34.5",
"@img/sharp-win32-arm64": "0.34.5",
"@img/sharp-win32-ia32": "0.34.5",
"@img/sharp-win32-x64": "0.34.5"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD",
"optional": true
}
}
}
{
"name": "broadsheet-card",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"validate:gold": "node scripts/validate-card-plan.js examples/gold-ai-tool-guide/card-plan.json examples/gold-ai-tool-guide/manifest.json && node scripts/validate-layout.js examples/gold-ai-tool-guide/index.html && node scripts/validate-contrast.js examples/gold-ai-tool-guide/index.html && node scripts/validate-manifest.js examples/gold-ai-tool-guide/manifest.json",
"render:gold": "node scripts/render.js examples/gold-ai-tool-guide/index.html examples/gold-ai-tool-guide/output",
"thumbnail:gold": "node scripts/make-thumbnail-preview.js examples/gold-ai-tool-guide/output"
},
"devDependencies": {
"playwright": "^1.54.0",
"sharp": "^0.34.0"
}
}
<div align="center">
Broadsheet Card
把原始内容发布成经过验证的复古报刊风图文卡片。

<img src="assets/banner.png" alt="Broadsheet Card 信息图封面" width="100%">
</div>
---
这是什么
Broadsheet Card 是一个 Agent Skill,用来把主题、大纲、文章或零散笔记转成一组 6 页复古报刊风图文卡片。
它把图文生成当成一次出版流程:先把内容编辑成 CardPlan,再选择封面方向、定义 ColorPlan、生成 HTML、渲染 PNG,并检查版式、对比度和 manifest。
这些卡片可以用于公众号贴图、小红书/Rednote 图文、社交媒体配图,也可以作为独立的出版感视觉资产。
为什么需要它
大多数 AI 图文失败在同一个地方:从主题直接跳到图片。
Broadsheet Card 强制补上中间的编辑和质检环节:
- 每页只承载一个明确结论
- 设计前先生成 3 个封面候选
- 固定复古报刊风的字体、色彩和版式规则
- 用 HTML-to-PNG 渲染,而不是只靠松散图片提示词
- 检查页数、对比度、manifest 和版式结构
<img src="assets/features.png" alt="Broadsheet Card 的内容编辑、视觉约束和质量闸门" width="100%">
快速开始
npx skills add geekjourneyx/broadsheet-card本地开发检查:
npm install
npm run validate:gold
npm run render:gold
npm run thumbnail:gold黄金样例位于:
examples/gold-ai-tool-guide/工作方式
<img src="assets/workflow.png" alt="Broadsheet Card 从内容简报到 PNG 输出的工作流程" width="100%">
项目结构
SKILL.md
references/
templates/
scripts/
examples/
evals/许可证
MIT
关于作者
| 个人主页 | geekjourney.dev |
| GitHub | geekjourneyx |
| @seekjourney | |
| 公众号 | 微信搜「极客杰尼」 |
Color System
Color is a decision system, not decoration. Before writing HTML, create a ColorPlan and derive CSS variables from it.
ColorPlan Contract
{
"color_read": "authoritative archival editorial",
"temperature": "warm-neutral",
"contrast_strength": "high",
"accent_strategy": "single oxblood editorial accent",
"palette": {
"background": "#f3e8d2",
"surface": "#f8eedb",
"primary_text": "#17130d",
"secondary_text": "#2b251a",
"muted_text": "#5b4b34",
"rule": "#a88e63",
"accent": "#9b1c16",
"accent_dark": "#64110d"
},
"usage_rules": {
"cover": "accent only for stamp and one keyword",
"inner_pages": "mostly ink and rule; red used at most once per page",
"final_page": "red save stamp allowed; checklist text remains black"
}
}Signature Palette
- Background:
#f3e8d2 - Surface:
#f8eedb - Primary text:
#17130d - Secondary text:
#2b251a - Muted text:
#5b4b34 - Rule:
#a88e63 - Accent:
#9b1c16 - Accent dark:
#64110d
Contrast
- Body text: 4.5:1 or higher.
- Large text: 3:1 or higher.
- Red text must be 28px or larger.
- Important text must sit on paper or surface, not on texture-heavy or image backgrounds.
- If color psychology conflicts with readability, readability wins.
Color Hierarchy
Use color in this order:
1. Establish the reading surface. 2. Create text contrast. 3. Build structure with rules and borders. 4. Add one accent to guide attention. 5. Add texture only after readability is secure.
Never start with gradients, decorative accents, or paper effects.
Anti-Default Rules
- Do not use purple/blue AI gradients.
- Do not add a second accent color unless the user provides a brand palette.
- Do not change accent colors between pages.
- Do not use red for both warning and decoration on the same page.
- Do not make the palette muddy, yellow, or low-contrast.
- Do not use pale beige text on beige background for elegance.
Copywriting
The writing voice is editorial: calm, concrete, judgmental when needed, and low on hype. Avoid influencer cliches, long slogans, and vague adjectives.
CardPlan Contract
Before HTML, create a JSON-like plan with:
{
"topic": "...",
"audience": "...",
"content_type": "knowledge-guide | consumer-warning | book-note | travel-route | opinion",
"cover_hook": "...",
"deck": "...",
"cover_candidates": [],
"selected_cover": "...",
"pages": [
{
"page": 1,
"role": "cover",
"recipe": "front-page-banner",
"headline": "...",
"deck": "...",
"main_point": "..."
}
]
}Required page roles:
cover → problem → framework → detail → comparison → checklistIf the content type needs a different editorial meaning, keep the six roles but rename the page emphasis inside main_point; do not randomly change the production structure.
Cover Hook Patterns
- 别再为 X 乱花钱
- 付费前,先问这 X 个问题
- 你以为是 X,其实是 Y
- 真正拉开差距的,不是 X
- 看懂这 X 点,再决定要不要 Y
Do not copy the user's topic directly as the cover title.
Page Title Patterns
Weak:
- 第一步:明确需求
- 功能对比
- 总结
Better:
- 先问:它替你省下了什么?
- 功能多,不等于值得买
- 保存这张清单,付费前看一遍
Body Rules
- One page, one main conclusion.
- Use short blocks, labels, numbered lists, and pull quotes.
- Avoid long paragraphs.
- Maximum recommended body copy: 180 Chinese characters per page.
Final Page CTA
Use a soft CTA only:
- 保存这 5 个问题,下次付费前先过一遍
- 评论区告诉我:你最近想买哪个工具?
- 下一篇拆:如何判断 AI 课程值不值得买
Cover System
The cover is the main battlefield. It is not a directory page; it is the feed stop signal.
Required Cover Process
Generate three candidates before choosing:
{
"cover_candidates": [
{
"type": "front-page-banner",
"headline": "...",
"deck": "...",
"why_it_works": "..."
},
{
"type": "investigative-special-report",
"headline": "...",
"deck": "...",
"why_it_works": "..."
},
{
"type": "classified-notice",
"headline": "...",
"deck": "...",
"why_it_works": "..."
}
],
"selected": "front-page-banner",
"selection_reason": "..."
}Candidate Types
Front Page Banner
Use for authoritative guides, frameworks, and general knowledge notes.
- Masthead at top
- Very large headline
- Deck under headline
- Red editorial stamp
- Small bottom teasers
Investigative Special Report
Use for business analysis, consumer warnings, course/product traps, and controversial decisions.
SPECIAL REPORTorCASE FILE- Headline with tension
- Evidence or dossier language
- Strong verdict strip
Classified Notice
Use for checklists, tool lists, practical guides, and saved references.
NOTICEorCLIP & SAVE- Keyword boxes
- Practical deck
- Strong final utility signal
Cover Rules
- Main headline: 8-18 Chinese characters preferred.
- Deck: 16-32 Chinese characters preferred.
- Main headline occupies 35-45% of visual weight.
- Must include masthead, issue/date/vol metadata, and one red editorial stamp.
- Must not include body paragraphs.
- Must remain readable at 30% thumbnail size.
- Do not copy the user's topic directly as the headline.
HTML Contract
The generated file must be a self-contained HTML document unless the user provides trusted assets.
Required DOM
<main id="deck">
<section class="page cover" id="page-1" data-page="1" data-role="cover">
</section>
<section class="page inner" id="page-2" data-page="2" data-role="problem">
</section>
</main>Each .page must be exactly 1080 x 1440 px.
Required Planning Artifacts
Each example or generated project should include:
card-plan.jsoncolor-plan.jsonmanifest.jsonindex.html
The HTML must embed a small CardPlan marker so validators can confirm planning was not skipped:
<script type="application/json" data-card-plan="true">...</script>
<script type="application/json" data-color-plan="true">...</script>Critical Text
Mark important readable text:
<h1 data-critical="true" data-min-font="96">...</h1>
<p data-critical="true" data-min-font="28">...</p>Validators use these attributes for safety checks.
Contrast Checks
Mark text that must pass contrast:
<p data-contrast-check="true" data-bg="#F3E8D2">...</p>Use hex colors in CSS variables and inline data-bg when the background is known.
Red Accent Tracking
Mark red accent elements:
<div class="stamp" data-accent="red">SAVE THIS</div>Each page should have at most two red accent elements.
Screenshot Contract
scripts/render.js captures each .page element individually. Do not rely on full-page screenshots or post-crop slicing.
Layout Recipes
Use these recipes as fixed page roles for the first version. Do not invent extra layouts unless the user request genuinely cannot fit the six-page sequence.
P1: Front Page Banner
Role: cover.
Structure:
- Top masthead: 10-14% of page height
- Headline: 35-45% of page height
- Deck: below headline, max two lines
- Optional stamp: lower right or overlapping headline edge
- Bottom teasers: three small editorial columns
Rules:
- No body paragraph on the cover.
- The headline must be readable at 30% size.
- Use one red accent cluster only.
P2: Lead Story
Role: problem or tension.
Structure:
- Folio
- Large title
- Lead paragraph
- Two-column body or main column plus sidebar
- Pull quote or verdict strip
Rules:
- Name the user's mistake or tension clearly.
- Body copy should feel like edited notes, not a pasted article.
P3: Numbered Framework
Role: core model.
Structure:
- Folio
- Title
- Three to five numbered blocks
- Bottom summary line
Rules:
- Number blocks must be scannable.
- Each block gets title plus one short explanation.
- Red can mark the number, not every heading.
P4: Feature Column
Role: deep explanation.
Structure:
- Folio
- Title
- One strong mechanism statement
- Main column plus annotation sidebar
- Pull quote or drop cap
Rules:
- Explain the one mechanism that makes the framework work.
- Avoid introducing a second framework.
P5: Split Verdict
Role: comparison.
Structure:
- Folio
- Title
- Two equal columns
- Center divider
- Bottom final verdict
Rules:
- Same number of points on both sides.
- Do not use bright red/green defaults.
- The bottom verdict must be stronger than the list items.
P6: Clipping Checklist
Role: summary.
Structure:
- Folio
- Title
- Checklist card
- Red save stamp
- Soft CTA or next-topic prompt
Rules:
- The checklist should be useful enough to save.
- Do not introduce a new color or layout language.
Quality Rubric
Hard Fail Conditions
Fail if any condition is true:
- No
CardPlanwas created before HTML. - No
ColorPlanwas created before HTML. - No three-candidate cover selection was created before HTML.
CardPlanpage roles do not follow cover/problem/framework/detail/comparison/checklist.- Output is not 1080 x 1440 per page.
- The deck has fewer or more than six pages unless requested.
- Critical text leaves the 72px safe area.
- Body critical text is below 28px.
- Cover headline is not readable at 30% preview.
- Red accent appears more than twice on any page.
- Important text sits directly on noisy texture or image.
- Page has DOM overflow.
- All inner pages use the same visual structure.
- The result looks like parchment, scrapbook, or generic Canva newspaper styling.
Scoring
Score 1-5:
- Cover impact
- Editorial logic
- Typography hierarchy
- Vintage Broadsheet fit
- Mobile/social readability
- Cross-page consistency
- Save/share value
- Rendering correctness
Good Output
A good output feels like a compact editorial publication adapted for mobile: strong cover, clear page rhythm, large readable Chinese text, controlled ink/paper palette, and a final page worth saving.
Vintage Broadsheet Design
This file defines the visual constitution for the skill. Vintage Broadsheet means editorial authority, structured hierarchy, paper-and-ink texture, and mobile-first readability. It does not mean an old parchment filter.
Canvas
- Width: 1080px
- Height: 1440px
- Ratio: 3:4
- Safe area: 72px minimum
- Preferred outer padding: 76-96px
- Page background may extend to the edge; critical text may not.
Design Tokens
Derive these values from ColorPlan in references/color.md. The defaults are the signature Vintage Broadsheet palette and should not be casually changed.
:root {
--paper: #f3e8d2;
--paper-soft: #f8eedb;
--paper-deep: #e2ceaa;
--ink: #17130d;
--ink-soft: #2b251a;
--ink-muted: #5b4b34;
--rule: #a88e63;
--rule-soft: #cfb98e;
--accent: #9b1c16;
--accent-dark: #64110d;
--font-title: "Noto Serif CJK SC", "Source Han Serif SC", "Songti SC", "SimSun", serif;
--font-body: "Noto Sans CJK SC", "Source Han Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
--font-meta: Georgia, "Times New Roman", serif;
}Palette Ratio
- Paper: 65-75%
- Ink: 18-25%
- Sepia/rules: 5-8%
- Red accent: 2-4%
Use red like an editor's mark, not a promotional label.
Design Dials
Set these before layout:
EDITORIAL_AUTHORITY: 9
VISUAL_DENSITY: 7
COLOR_TEMPERATURE: warm-neutral
CONTRAST_STRENGTH: high
VINTAGE_INTENSITY: 7
SOCIAL_HOOK_INTENSITY: 8If a dial conflicts with readability, readability wins.
Typography
- Cover headline: 96-150px
- Inner page title: 64-92px
- Deck/subtitle: 36-52px
- Body: 28-38px
- Caption: 20-26px
- Folio/meta: 16-22px
- Decorative microtext: 12-16px only when non-semantic
Line height:
- Cover headline: 0.92-1.02
- Page title: 1.02-1.12
- Body: 1.35-1.55
- Caption: 1.25-1.4
Layout Discipline
- Use a 12-column grid.
- Simulate 2-4 editorial zones, not six tiny newspaper columns.
- Every page needs one dominant focal point.
- Avoid more than four active content zones on a page.
- Do not center everything.
- Alternate page energy across the carousel.
Components
Allowed components:
- masthead
- folio
- deck
- thin rule
- bold rule
- double rule
- pull quote
- sidebar
- stamp
- number block
- clipping card
- checklist
- caption
- teaser
Avoid random stickers, emoji, glassmorphism, neon effects, coffee stains, parchment scrolls, and meaningless English filler.
Texture
- Paper grain opacity should stay subtle.
- Texture must not reduce body text contrast.
- Body text should sit on a clean surface.
- Halftone and sepia effects are optional and should support hierarchy.
Anti-Patterns
Fail the design if it looks like:
- yellow parchment poster
- generic Canva newspaper template
- old Western wanted poster
- scrapbook collage
- article screenshot with tiny text
- random English decorative blocks
- six pages with the same title/paragraph/footer structure
#!/usr/bin/env node
import fs from "node:fs/promises";
import path from "node:path";
const inputDir = process.argv[2];
const outputDir = process.argv[3] || path.join(inputDir || "", "thumbnails");
if (!inputDir) {
console.error("Usage: make-thumbnail-preview.js <png-dir> [output-dir]");
process.exit(2);
}
let sharp;
try {
({ default: sharp } = await import("sharp"));
} catch {
console.error("sharp is not installed. Run npm install before generating thumbnail previews.");
process.exit(1);
}
await fs.mkdir(outputDir, { recursive: true });
const files = (await fs.readdir(inputDir)).filter((file) => file.endsWith(".png"));
for (const file of files) {
await sharp(path.join(inputDir, file))
.resize({ width: 324 })
.png()
.toFile(path.join(outputDir, file.replace(".png", "-30pct.png")));
}
console.log(`Generated ${files.length} thumbnail previews in ${outputDir}`);
#!/usr/bin/env node
import fs from "node:fs/promises";
import path from "node:path";
import { pathToFileURL } from "node:url";
const input = process.argv[2];
const outputDir = process.argv[3] || "output";
if (!input) {
console.error("Usage: render.js <index.html> [output-dir]");
process.exit(2);
}
let chromium;
try {
({ chromium } = await import("playwright"));
} catch {
console.error("Playwright is not installed. Run npm install before rendering PNG files.");
process.exit(1);
}
await fs.mkdir(outputDir, { recursive: true });
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({
viewport: { width: 1080, height: 1440 },
deviceScaleFactor: 1
});
await page.goto(pathToFileURL(path.resolve(input)).href, { waitUntil: "networkidle" });
await page.evaluate(async () => {
if (document.fonts?.ready) await document.fonts.ready;
});
const pageCount = await page.locator(".page").count();
for (let index = 0; index < pageCount; index += 1) {
const locator = page.locator(".page").nth(index);
const role = await locator.getAttribute("data-role");
const suffix = index === 0 ? "cover" : role || `page-${index + 1}`;
const filename = `page-${String(index + 1).padStart(2, "0")}-${suffix}.png`;
await locator.screenshot({
path: path.join(outputDir, filename),
animations: "disabled"
});
}
await browser.close();
console.log(`Exported ${pageCount} pages to ${outputDir}`);
#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
const cardPlanFile = process.argv[2];
const manifestFile = process.argv[3];
if (!cardPlanFile) {
console.error("Usage: validate-card-plan.js <card-plan.json> [manifest.json]");
process.exit(2);
}
const plan = JSON.parse(fs.readFileSync(cardPlanFile, "utf8"));
const issues = [];
const requiredRoles = ["cover", "problem", "framework", "detail", "comparison", "checklist"];
const requiredRecipes = [
"front-page-banner",
"lead-story",
"numbered-framework",
"feature-column",
"split-verdict",
"clipping-checklist"
];
if (!plan.topic) issues.push("topic is required");
if (!plan.audience) issues.push("audience is required");
if (plan.style !== "vintage-broadsheet") issues.push("style must be vintage-broadsheet");
if (!plan.cover_hook) issues.push("cover_hook is required");
if (!plan.deck) issues.push("deck is required");
if (!Array.isArray(plan.cover_candidates) || plan.cover_candidates.length !== 3) {
issues.push("cover_candidates must contain exactly 3 candidates");
}
if (!plan.selected_cover) issues.push("selected_cover is required");
const coverHanCount = countHan(plan.cover_hook || "");
if (coverHanCount && (coverHanCount < 8 || coverHanCount > 18)) {
issues.push(`cover_hook should prefer 8-18 Chinese chars, found ${coverHanCount}`);
}
if (!Array.isArray(plan.pages) || plan.pages.length !== 6) {
issues.push(`pages must contain 6 entries, found ${Array.isArray(plan.pages) ? plan.pages.length : 0}`);
} else {
plan.pages.forEach((page, index) => {
const expectedPage = index + 1;
if (page.page !== expectedPage) issues.push(`page ${expectedPage}: page field must be ${expectedPage}`);
if (page.role !== requiredRoles[index]) {
issues.push(`page ${expectedPage}: role must be ${requiredRoles[index]}, found ${page.role}`);
}
if (page.recipe !== requiredRecipes[index]) {
issues.push(`page ${expectedPage}: recipe must be ${requiredRecipes[index]}, found ${page.recipe}`);
}
if (!page.headline) issues.push(`page ${expectedPage}: headline is required`);
if (!page.main_point) issues.push(`page ${expectedPage}: main_point is required`);
});
}
if (manifestFile) {
const manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
if (manifest.card_plan && path.basename(manifest.card_plan) !== path.basename(cardPlanFile)) {
issues.push("manifest card_plan does not point to the provided card-plan.json");
}
if (manifest.cover_title && manifest.cover_title !== plan.cover_hook) {
issues.push("manifest cover_title must match card-plan cover_hook");
}
}
if (issues.length) {
console.error("CardPlan validation failed:");
for (const issue of issues) console.error(`- ${issue}`);
process.exit(1);
}
console.log("CardPlan validation passed");
function countHan(value) {
return [...value].filter((char) => /\p{Script=Han}/u.test(char)).length;
}
#!/usr/bin/env node
import fs from "node:fs";
const file = process.argv[2];
if (!file) {
console.error("Usage: validate-contrast.js <index.html>");
process.exit(2);
}
const html = fs.readFileSync(file, "utf8");
const issues = [];
const checks = [...html.matchAll(/<([a-z0-9]+)\b([^>]*data-contrast-check="true"[^>]*)>/gi)];
if (checks.length === 0) {
issues.push("no data-contrast-check elements found");
}
for (const check of checks) {
const attrs = check[2];
const color = attr(attrs, "data-color") || "#17130d";
const bg = attr(attrs, "data-bg") || "#f3e8d2";
const min = Number(attr(attrs, "data-min-contrast") || 4.5);
const ratio = contrastRatio(color, bg);
if (ratio < min) {
issues.push(`${color} on ${bg} ratio ${ratio.toFixed(2)} below ${min}`);
}
}
if (issues.length) {
console.error("Contrast validation failed:");
for (const issue of issues) console.error(`- ${issue}`);
process.exit(1);
}
console.log(`Contrast validation passed: ${checks.length} checks`);
function attr(source, name) {
return (source.match(new RegExp(`${name}="([^"]+)"`)) || [])[1];
}
function hexToRgb(hex) {
const clean = hex.replace("#", "").trim();
const full = clean.length === 3
? clean.split("").map((char) => char + char).join("")
: clean;
const value = Number.parseInt(full, 16);
return {
r: (value >> 16) & 255,
g: (value >> 8) & 255,
b: value & 255
};
}
function channelToLinear(value) {
const normalized = value / 255;
return normalized <= 0.03928
? normalized / 12.92
: Math.pow((normalized + 0.055) / 1.055, 2.4);
}
function luminance(rgb) {
return 0.2126 * channelToLinear(rgb.r) +
0.7152 * channelToLinear(rgb.g) +
0.0722 * channelToLinear(rgb.b);
}
function contrastRatio(foreground, background) {
const l1 = luminance(hexToRgb(foreground));
const l2 = luminance(hexToRgb(background));
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
const file = process.argv[2];
if (!file) {
console.error("Usage: validate-layout.js <index.html>");
process.exit(2);
}
const html = fs.readFileSync(file, "utf8");
const css = loadCss(html, path.dirname(file));
const issues = [];
const pageMatches = [...html.matchAll(/<section\b[^>]*class="[^"]*\bpage\b[^"]*"[^>]*>[\s\S]*?<\/section>/g)];
const expectedRoles = ["cover", "problem", "framework", "detail", "comparison", "checklist"];
if (pageMatches.length !== 6) {
issues.push(`expected 6 .page sections, found ${pageMatches.length}`);
}
if (!/width:\s*1080px/.test(css) || !/height:\s*1440px/.test(css)) {
issues.push("page CSS must define width: 1080px and height: 1440px");
}
pageMatches.forEach((match, index) => {
const pageHtml = match[0];
const pageNo = index + 1;
const attrs = match[0].match(/<section\b([^>]*)>/i)?.[1] || "";
const role = (attrs.match(/data-role="([^"]+)"/) || [])[1];
if (role !== expectedRoles[index]) {
issues.push(`page ${pageNo}: data-role must be ${expectedRoles[index]}, found ${role || "missing"}`);
}
const accentCount = (pageHtml.match(/data-accent="red"/g) || []).length;
if (accentCount > 2) {
issues.push(`page ${pageNo}: red accent count ${accentCount} exceeds 2`);
}
const criticalTags = [...pageHtml.matchAll(/<([a-z0-9]+)\b[^>]*data-critical="true"[^>]*>/gi)];
if (criticalTags.length === 0) {
issues.push(`page ${pageNo}: no critical text marked`);
}
for (const tag of criticalTags) {
const attrs = tag[0];
const minFont = Number((attrs.match(/data-min-font="(\d+)"/) || [])[1] || 0);
if (minFont && minFont < 28 && pageNo !== 1) {
issues.push(`page ${pageNo}: critical text data-min-font ${minFont} is below 28`);
}
}
});
const cover = pageMatches[0]?.[0] || "";
const coverTitle = stripTags((cover.match(/<h1\b[^>]*data-critical="true"[^>]*>([\s\S]*?)<\/h1>/i) || [])[1] || "").trim();
const chineseChars = [...coverTitle].filter((char) => /\p{Script=Han}/u.test(char)).length;
if (chineseChars && (chineseChars < 8 || chineseChars > 18)) {
issues.push(`cover headline should prefer 8-18 Chinese chars, found ${chineseChars}`);
}
if (!/data-card-plan="true"/.test(html)) {
issues.push("missing embedded CardPlan marker data-card-plan=\"true\"");
}
if (!/data-color-plan="true"/.test(html)) {
issues.push("missing embedded ColorPlan marker data-color-plan=\"true\"");
}
if (issues.length) {
console.error("Layout validation failed:");
for (const issue of issues) console.error(`- ${issue}`);
process.exit(1);
}
console.log(`Layout validation passed: ${pageMatches.length} pages`);
function loadCss(source, baseDir) {
const links = [...source.matchAll(/<link\b[^>]*href="([^"]+\.css)"[^>]*>/g)];
const chunks = [];
for (const link of links) {
const cssPath = path.resolve(baseDir, link[1]);
if (fs.existsSync(cssPath)) chunks.push(fs.readFileSync(cssPath, "utf8"));
}
chunks.push(source);
return chunks.join("\n");
}
function stripTags(value) {
return value.replace(/<[^>]+>/g, "").replace(/\s+/g, "");
}
#!/usr/bin/env node
import fs from "node:fs";
const file = process.argv[2];
if (!file) {
console.error("Usage: validate-manifest.js <manifest.json>");
process.exit(2);
}
const manifest = JSON.parse(fs.readFileSync(file, "utf8"));
const issues = [];
if (manifest.format !== "broadsheet-card-set") issues.push("format must be broadsheet-card-set");
if (manifest.size !== "1080x1440") issues.push("size must be 1080x1440");
if (manifest.ratio !== "3:4") issues.push("ratio must be 3:4");
if (manifest.style !== "vintage-broadsheet") issues.push("style must be vintage-broadsheet");
if (!Array.isArray(manifest.files) || manifest.files.length !== 6) {
issues.push("files must list 6 PNG files");
}
const expectedFiles = [
"output/page-01-cover.png",
"output/page-02-problem.png",
"output/page-03-framework.png",
"output/page-04-detail.png",
"output/page-05-comparison.png",
"output/page-06-checklist.png"
];
if (Array.isArray(manifest.files)) {
expectedFiles.forEach((file, index) => {
if (manifest.files[index] !== file) {
issues.push(`files[${index}] must be ${file}`);
}
});
}
if (!manifest.cover_title) issues.push("cover_title is required");
if (manifest.card_plan !== "card-plan.json") issues.push("card_plan must be card-plan.json");
if (manifest.color_plan !== "color-plan.json") issues.push("color_plan must be color-plan.json");
if (issues.length) {
console.error("Manifest validation failed:");
for (const issue of issues) console.error(`- ${issue}`);
process.exit(1);
}
console.log("Manifest validation passed");
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=1080, initial-scale=1">
<title>Broadsheet Card</title>
<link rel="stylesheet" href="../../templates/components.css">
</head>
<body>
<main id="deck">
<section class="page cover" id="page-1" data-page="1" data-role="cover">
<!-- Generate pages following references/html-contract.md. -->
</section>
</main>
</body>
</html>
:root {
--paper: #f3e8d2;
--paper-soft: #f8eedb;
--paper-deep: #e2ceaa;
--ink: #17130d;
--ink-soft: #2b251a;
--ink-muted: #5b4b34;
--rule: #a88e63;
--rule-soft: #cfb98e;
--accent: #9b1c16;
--accent-dark: #64110d;
--font-title: "Noto Serif CJK SC", "Source Han Serif SC", "Songti SC", "SimSun", serif;
--font-body: "Noto Sans CJK SC", "Source Han Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
--font-meta: Georgia, "Times New Roman", serif;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
background: #d8c49d;
color: var(--ink);
font-family: var(--font-body);
}
#deck {
display: grid;
gap: 40px;
padding: 40px;
}
.page {
position: relative;
width: 1080px;
height: 1440px;
overflow: hidden;
padding: 84px 76px 72px;
background: var(--paper);
color: var(--ink);
}
.page::before {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
opacity: 0.18;
background:
radial-gradient(circle at 20% 30%, rgba(0, 0, 0, 0.07) 0 1px, transparent 1.5px),
radial-gradient(circle at 70% 60%, rgba(255, 255, 255, 0.28) 0 1px, transparent 1.5px);
background-size: 18px 18px, 23px 23px;
mix-blend-mode: multiply;
}
.page > * {
position: relative;
z-index: 1;
}
.grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
column-gap: 18px;
row-gap: 22px;
}
.masthead {
font-family: var(--font-meta);
text-align: center;
}
.masthead__top,
.folio {
display: flex;
justify-content: space-between;
gap: 18px;
color: var(--ink-muted);
font-family: var(--font-meta);
font-size: 18px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.masthead__name {
margin: 16px 0 12px;
font-family: var(--font-title);
font-size: 54px;
font-weight: 800;
letter-spacing: 0.02em;
text-transform: uppercase;
}
.rule-thin {
border-top: 1px solid var(--rule);
}
.rule-bold {
border-top: 4px solid var(--ink);
}
.rule-double {
height: 6px;
border-top: 1px solid var(--ink);
border-bottom: 3px solid var(--ink);
}
.cover-title {
margin: 70px 0 26px;
font-family: var(--font-title);
font-size: 124px;
line-height: 0.98;
font-weight: 900;
letter-spacing: 0;
}
.page-title {
margin: 34px 0 24px;
font-family: var(--font-title);
font-size: 76px;
line-height: 1.06;
font-weight: 900;
letter-spacing: 0;
}
.deck {
max-width: 760px;
color: var(--ink-soft);
font-family: var(--font-body);
font-size: 42px;
line-height: 1.25;
font-weight: 700;
}
.lead {
font-size: 34px;
line-height: 1.42;
font-weight: 700;
}
.body {
color: var(--ink-soft);
font-size: 31px;
line-height: 1.48;
}
.caption {
color: var(--ink-muted);
font-size: 22px;
line-height: 1.35;
}
.teasers {
position: absolute;
left: 76px;
right: 76px;
bottom: 72px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
border-top: 4px solid var(--ink);
padding-top: 22px;
}
.teaser {
border-right: 1px solid var(--rule);
padding-right: 18px;
}
.teaser:last-child {
border-right: 0;
}
.teaser strong {
display: block;
margin-bottom: 8px;
font-family: var(--font-title);
font-size: 26px;
}
.stamp {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 150px;
min-height: 74px;
border: 4px solid var(--accent);
color: var(--accent);
font-family: var(--font-meta);
font-size: 25px;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
transform: rotate(-4deg);
}
.cover .stamp {
position: absolute;
right: 106px;
top: 680px;
}
.sidebar {
padding: 26px;
background: var(--paper-soft);
border: 1px solid var(--rule);
}
.pullquote {
margin: 30px 0;
padding: 20px 28px;
border-left: 8px solid var(--accent);
font-family: var(--font-title);
font-size: 40px;
line-height: 1.22;
font-weight: 800;
}
.number-block {
padding: 24px 0;
border-top: 1px solid var(--rule);
}
.number-block__num {
color: var(--accent);
font-family: var(--font-meta);
font-size: 54px;
font-weight: 900;
}
.number-block__title {
font-family: var(--font-title);
font-size: 38px;
line-height: 1.12;
font-weight: 900;
}
.split {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 34px;
}
.split__col {
min-height: 680px;
padding: 28px;
border: 1px solid var(--rule);
background: var(--paper-soft);
}
.split__label {
margin-bottom: 22px;
font-family: var(--font-meta);
font-size: 22px;
font-weight: 900;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.verdict {
margin-top: 34px;
padding: 22px 28px;
border-top: 4px solid var(--ink);
border-bottom: 1px solid var(--ink);
font-family: var(--font-title);
font-size: 42px;
line-height: 1.18;
font-weight: 900;
}
.checklist {
margin: 38px 0 0;
padding: 0;
list-style: none;
}
.checklist li {
display: grid;
grid-template-columns: 74px 1fr;
gap: 22px;
padding: 24px 0;
border-top: 1px solid var(--rule);
font-size: 34px;
line-height: 1.28;
font-weight: 750;
}
.checklist span {
color: var(--accent);
font-family: var(--font-meta);
font-weight: 900;
}
[
{
"id": 1,
"prompt": "把这段关于 AI 工具付费判断的内容做成 6 页 Vintage Broadsheet 风格图文卡片,要能用于小红书或公众号贴图:不要看工具宣传得多强,要看它是否进入你的高频工作流。值得付费的工具要么节省可感知时间,要么替代稳定成本,要么带来更好的输出质量。功能多不等于值得买。",
"expected": "Creates a CardPlan, three cover candidates, ColorPlan, self-contained HTML, six 1080x1440 cards, manifest, and passes validation."
},
{
"id": 2,
"prompt": "我有一篇公众号文章,主题是如何判断一门副业课是不是割韭菜。请把核心观点做成一组复古报刊感贴图,适合插在文章中间,不要绑定小红书平台。",
"expected": "Asks for the article text or core points before final cards, or produces only a planning brief; keeps platform-neutral naming and avoids Rednote/Xiaohongshu-specific output assumptions."
},
{
"id": 3,
"prompt": "做一套高级复古报纸风图片,主题是普通人如何投资,越详细越好,所有建议都写上。",
"expected": "Refuses or asks for more factual input because the request is broad and financially sensitive, rejects cramming long advice into cards, and preserves readability constraints."
}
]