
Guizang Social Card Skill
- 4k installs
- 5.9k repo stars
- Updated July 1, 2026
- op7418/guizang-social-card-skill
guizang-social-card-skill is an agent skill that turns articles, scripts, and screenshots into Guizang-style Xiaohongshu carousels and WeChat cover pairs via HTML/CSS layout recipes.
About
guizang-social-card-skill creates polished social image packages for Xiaohongshu, Rednote, WeChat Official Account covers, and platform thumbnails using Guizang Swiss and editorial magazine layout principles without modifying the original PPT skill. It produces 3:4 carousel sets with cover plus content pages and paired WeChat 21:9 main plus 1:1 square covers composed in one HTML file for visual checking. A capability circle maps eleven Rednote categories into strong end-to-end, text-only with user images, and out-of-scope buckets such as daily OOTD body shots or dreamcore aesthetics, with honest pushback when requests fall outside scope. The workflow runs intake for platforms, source text, category routing via category-cookbook.md, and a one-shot image sourcing prompt when no photos are supplied. Story extraction plans 5-9 Rednote pages with one idea per page and WeChat short-title derivation for the square cover. Required references cover platform-specs, style-system, layout-recipes, components, and qa-checklist. Core principle is expression first: each page must communicate one glance takeaway with evidence screenshots and compressed copy.
- Produces Xiaohongshu 3:4 carousels and paired WeChat 21:9 plus 1:1 cover HTML layouts.
- Routes eleven Rednote categories through a capability circle with honest out-of-scope pushback.
- References platform-specs, style-system, layout-recipes, components, and qa-checklist files.
- One-shot intake prompt offers user photos, stock search, or AI generation for evidence images.
- Expression-first page planning with 5-9 Rednote pages carrying one idea each.
Guizang Social Card Skill by the numbers
- 4,000 all-time installs (skills.sh)
- +270 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #122 of 1,335 Generative Media skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
guizang-social-card-skill capabilities & compatibility
- Capabilities
- rednote category routing and capability circle s · html/css carousel and wechat cover pair generati · screenshot treatment and image overlay reference · story extraction into 5 9 single idea pages with
- Use cases
- image generation · copywriting · marketing
What guizang-social-card-skill says it does
Create polished social card packages for Xiaohongshu/Rednote, WeChat Official Account, article covers
Expression comes first. The goal is not to squeeze text into posters
npx skills add https://github.com/op7418/guizang-social-card-skill --skill guizang-social-card-skillAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4k |
|---|---|
| repo stars | ★ 5.9k |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 1, 2026 |
| Repository | op7418/guizang-social-card-skill ↗ |
How do I turn long-form content into polished Xiaohongshu carousels and paired WeChat 21:9 plus 1:1 covers with Swiss editorial layouts?
Generate Guizang-style Xiaohongshu carousel sets and WeChat official account 21:9 plus 1:1 cover pairs from articles, scripts, screenshots, or photos using HTML/CSS layout recipes.
Who is it for?
Creators producing Rednote or WeChat social cards who need structured Guizang editorial layouts from articles and screenshots.
Skip if: Skip for full slide decks, long-form video generation, or pure photography showcase posts outside the capability circle.
When should I use this skill?
User asks for 小红书图文, Rednote carousels, WeChat official account covers, Swiss Style social cards, or magazine-style social images.
What you get
A page-planned social image package with cover hooks, per-page visual arguments, exported carousel images, and QA-checked layout files.
- Carousel image set
- WeChat cover pair HTML and exported images
By the numbers
- Supports 11 common Rednote category routes
- Typical Rednote posts use 5-9 pages
Files
Guizang Social Card Skill
Create polished social card packages for Xiaohongshu/Rednote, WeChat Official Account, article covers, and platform thumbnails.
This skill is self-contained. It borrows visual principles from the Guizang PPT style system, but it must not edit the original PPT skill, its templates, or its references. If the original PPT skill is available, you may read it for reference only. Put all generated work in the current project or in the user-requested output folder.
What To Produce
Use this skill for:
- Social card / carousel image sets: cover plus content pages, especially Xiaohongshu/Rednote 3:4.
- WeChat Official Account cover pairs: one
21:9main cover plus one1:1square cover, composed together in the same HTML for visual checking. - Screenshot-heavy product posts, article covers, tutorial carousels, outdoor/lifestyle notes, AI/product update explainers.
- Social images that need Guizang-style Swiss or editorial magazine layouts.
Do not use this skill for:
- Full slide decks or horizontal PPT websites. Use the PPT skill for that.
- Long-form video generation. Use a video skill for that.
- Pure image editing with no layout or article extraction requirement.
Rednote Category Capability (capability circle)
The 11 most-common Rednote (小红书) categories fall into three buckets. See references/category-cookbook.md for the recipe-by-recipe routing.
Strong end-to-end (text, structure, and image story all in scope):
- 旅行 (Travel), 职场 (Workplace), 推荐 (Recommended, after specifying a subtype).
Strong on text & structure; image needs to come from the user or a sourced library:
- 游戏 (Game), 影视 (Film/TV), 美食 食谱方向 (Food — recipes only), 彩妆 教程方向 (Makeup — tutorials only), 健身 (Fitness), 家居 (Home), 穿搭 精选方向 (Outfit — capsule/essay only).
Outside scope — push back honestly rather than promise a result:
- 美食 菜品大片摆盘 (food-photography showcase).
- 穿搭 日常 OOTD 全身 (daily OOTD body shots; we cannot generate or simulate).
- 情感 梦核 / 氛围感装饰风 (dreamcore / aesthetic-light styling — clashes with both Editorial and Swiss).
- Y2K / 千禧辣妹 / 哥特萝莉 / kawaii decorated aesthetics.
- Pure photography showcase posts where the image is the entire deliverable.
When a request falls in the third bucket, name what we cannot do at intake — do not silently retrofit a layout that misses the user's intent.
Core Principle
Expression comes first. The goal is not to squeeze text into posters; it is to turn the source into a clear visual argument.
For each page, decide:
- What should the viewer understand in one glance?
- What evidence, screenshot, or image supports it?
- Which words must be large, and which can become captions or metadata?
- What can be removed because it belongs in the post body, not the image?
Required References
Read these files as needed:
references/platform-specs.mdfor exact ratios, output sizes, and naming.references/style-system.mdfor Guizang editorial and Swiss visual rules.references/theme-presets.mdwhen choosing electronic-magazine palettes or Swiss accent palettes.references/layout-recipes.mdwhen selecting carousel/social-card/WeChat page structures.references/components.mdfor the shared component spec: font stacks, type scale, minimum readable sizes, Chinese title length bands, Swiss card-fill mutual-exclusion rule, image-container ratio classes, spacing tokens, and Lucide icon rules.references/background-systems.mdwhen building electronic-magazine WebGL/ink/paper backgrounds.references/portrait-fill.mdwhen adapting layouts to 3:4 and avoiding under-filled vertical space.references/content-planning.mdfor cover hooks, page breakdown, and copy compression.references/production-workflow.mdfor HTML/CSS rendering and image handling.references/image-overlay.mdwhenever text sits on top of a photo: photo qualification, localized tint fallback, and face / subject avoidance via multimodal subject mapping.references/screenshot-treatment.mdwhen the user supplies an app / web / code / dashboard screenshot — picks.frame-shotover.frame-img, sets corners/shadow/bg/inset, decides on.device-browseror.device-phonechrome.references/map-component.mdwhen the content has spatial relationships (travel route, store locations, walking tour) — real routes default to Mapbox Static or OSM static tiles; schematic SVG is only for conceptual / illustrative maps. Pins are HTML overlays; never use live JS maps.references/title-shortener.mdwhen the task is a WeChat 21:9+1:1 cover pair, or any cross-platform reuse — derives the 1:1 short title from the long one (5-step extraction, 4 patterns, anti-patterns, sizing on.poster.square).references/category-cookbook.mdto route a user-named Rednote category (旅行 / 职场 / 游戏 / 影视 / 彩妆 / 美食 / 穿搭 / 家居 / 健身 / 情感 / 推荐) to applicable recipes and to confirm scope.references/qa-checklist.mdbefore delivering final images.
Workflow
1. Intake
Gather only the missing information that changes the output:
- Target platforms and ratios.
- Source text, subtitles, article, or title.
- Rednote category — if the user names one of the 11 common types (旅行 / 职场 / 游戏 / 影视 / 美食 / 彩妆 / 穿搭 / 家居 / 健身 / 情感 / 推荐), route via
references/category-cookbook.mdto find the right recipes and to confirm the request is inside the capability circle (see "Rednote Category Capability" above). If a request lands in the outside-scope bucket, surface that to the user before designing, do not silently retrofit. - Supplied images/screenshots and where each should appear. For News / Tutorial / Data / Review content, actively prompt for screenshots or photos — they are the evidence layer. A poster with no real artifact tends to read as filler.
- If the user supplies only text (no images at all), ask once before designing:
这篇我需要 1-2 张图。三种走法:
A. 你自己有照片 / 截图,传给我(推荐——最不"AI 感")
B. 我去 Pexels / Unsplash / Flickr 帮你找
C. 用 AI 生成Recommend A in one line — your own photo is what makes a poster not look AI-generated. Accept whatever the user picks (including "都行你看着办") and proceed. Do not re-prompt later, do not keep nudging toward A across multiple turns. This question is one-shot.
- Preferred style if specified: Swiss Style, magazine/editorial, tech, outdoor, etc.
- Hard constraints: title text, no image on 1:1 cover, must include a hardware photo, keep screenshot readable, and so on.
If the user has already supplied enough context, proceed with reasonable assumptions.
If the content involves current product releases, policies, prices, claims, or news, verify unstable facts with browsing and cite sources in the final response.
2. Extract The Story
Turn the source into a page plan before designing.
For Rednote:
- Page 1 is the cover hook.
- Pages 2-N each carry one idea only.
- Use 5-9 pages for most posts. Compress or combine pages when lower areas become empty.
- Keep the post body for nuance; images should carry hooks, comparisons, checklists, and sharp takeaways.
For WeChat:
- Always produce a paired system:
21:9main cover and1:1square cover. - Build both covers in the same HTML file and add a combined preview section so their visual relationship can be checked together.
21:9keeps the full or near-full title, subtitle, and one strong visual relation.1:1uses a simplified short title derived from the long title: big centered type, no image by default, no cramped subtitles.
3. Choose Style Mode
Pick one mode per package. The two systems are not bound to specific content types — what changes is the visual stance, not which topic you can talk about. A workplace essay can be Editorial; a travel ledger can be Swiss. Pick by the feeling you want, not by category lookup.
Editorial Magazine x E-ink brings:
- Serif/Songti display + quiet sans body, paper + ink palette.
- Atmosphere layer (paper grain / ink wash / WebGL canvas) over a warm paper base.
- Ledger rows, marginalia, pull quotes, large photo wells — magazine-feature feel.
- Best when you want the page to feel slow, considered, hand-set.
Swiss International brings:
- Inter / Helvetica feel, very light display at large sizes, mono labels at small.
- Strict left-aligned grid, hairline rules, one high-saturation accent.
- Card-fill matrices, KPI towers, h-bar charts, numbered statements — system / data feel.
- Best when you want the page to feel engineered, quantified, decisive.
If both feel viable for a piece of content, the question becomes editorial intent: "is this a feature story or a release note?" That decides the mode, not the topic itself.
Do not mix the two visual systems inside the same image set unless the user explicitly asks for a hybrid.
Then pick one theme:
- Editorial Magazine x E-ink uses one of 6 magazine palettes: Ink Classic, Indigo Porcelain, Forest Ink, Kraft Paper, Dune, or Midnight Ink (the only dark variant; reserved for game key art / night photography / cinematic covers).
- Swiss International uses one of 4 accent palettes: IKB Blue, Lemon Yellow, Lemon Green, or Safety Orange.
Read references/theme-presets.md for exact CSS tokens. Do not invent arbitrary colors unless the user has a strict brand requirement.
4. Plan Pages
Create a concise internal plan:
Page 01 / cover / hook / image source / layout intent
Page 02 / point / key copy / visual evidence / layout intent
...When the user asks for approval, show this plan before rendering. Otherwise use it internally and proceed.
Use references/layout-recipes.md to choose page structures. Avoid making every page a repeated title-plus-card layout.
For 3:4 images, check references/portrait-fill.md before coding. A short table or ledger must be expanded into a full portrait composition with a quote column, image evidence, marginalia, larger rows, or a background hero zone.
4.5. Copy The Seed Template
Do not write HTML from scratch. Pick one seed template based on the style mode chosen in Step 3:
- Editorial Magazine × E-ink → copy
assets/template-editorial-card.htmlinto the task folder asindex.html. - Swiss International → copy
assets/template-swiss-card.htmlinto the task folder asindex.html.
The seed already wires up: font loading, theme tokens, all three poster sizes (.poster.xhs / .poster.square / .poster.wide), the pair-preview frame, grain/background layers, and all class definitions referenced by the layout recipes.
Set the theme/accent on the <html> element:
- Editorial:
<html data-theme="ink-classic | indigo-porcelain | forest-ink | kraft-paper | dune | midnight-ink">. - Swiss:
<html data-accent="ikb | lemon-yellow | lemon-green | safety-orange">.
Replace the single placeholder poster after <!-- POSTERS_HERE --> with one <section class="poster ..."> block per page, each carrying the HTML skeleton from a chosen Layout Recipe (M01-M16 for Editorial, S01-S12 for Swiss). Never load the wrong template's class system: Editorial recipes assume serif display + ledger/marginalia/pipeline-v; Swiss recipes assume Inter + card-fills + matrix/h-bar/kpi-tower. Mixing them silently breaks the layout.
5. Build And Render
Default implementation pattern:
- Create a task folder in the current workspace, for example
social-card-<slug>/. - Put source images in
assets/. - Start from the seed template copied in Step 4.5, not a blank file. Prefer changing only the
<!-- POSTERS_HERE -->region page-to-page. If a task needs custom layout CSS, add one clearly named task-scoped block in the copied file and keep semantic defaults reset (figure { margin:0; }, no browser-default spacing surprises). - Use Playwright or a browser screenshot tool to export each
.posteror.covernode. - Save rendered images in
output/. - Verify dimensions and inspect the rendered PNGs.
- Keep
node validate-social-deck.mjs <task-dir>available for auto-check passes. It checks overflow (R1), footer collision (R2), Swiss bold display (R3), minimum font size (R4), 4-band density (R5),.h-xlline caps (R6), and browser-default figure margin drift (R7). Exit code 1 on any FAIL — fix before final delivery when auto-check is requested. WARN is advisory but read it.
Do not place visible instructions, keyboard shortcuts, or usage explanations inside the images.
For Editorial Magazine x E-ink, use a layered background system. Prefer a subtle WebGL ink-flow canvas or a frozen procedural canvas plus paper grain. Read references/background-systems.md; do not rely on a flat beige background, and do not add page-wide grid/dot backgrounds.
6. Image And Screenshot Handling
When the user provides screenshots:
- Preserve screenshot content unless the user asks for redesign.
- Prefer programmatic framing: target-ratio canvas, safe padding, clean background, readable screenshot.
- Do not stretch screenshots.
- If screenshot clarity matters, enlarge the screenshot area and reduce nearby text.
Text-On-Image Composition
Whenever a poster places text on top of a photo (full-bleed cover, large image well, generated-image overlay), follow references/image-overlay.md:
- Selection first, tint only if needed. A photo covering ≥60% of the canvas must first pass the quiet-zone and light tests in
image-overlay.md. Compose without a mask first; if the thumbnail check fails, add only a localized, image-toned tint around the title area. Do not default to full-canvas falloffs. - Subject mapping is mandatory. Before placing the title, read the image with the Read tool, describe in plain language where the subject's face/focal feature sits, and record the subject map as an HTML comment next to the hero block. Place text only in the documented safe zones.
- Crop discipline — set `object-position` inline on every photo. The template default (
center 50%) is a fallback, not a recommendation. For every<img>, decide based on subject location and write it inline: e.g.style="object-position:center 62%"for mid-body subjects,center 30%for sky-heavy landscapes with horizon-line subjects,center 70%for foreground gear. See the table inreferences/components.mdfor ranges andimage-overlay.mdfor face-photo specifics. Skipping this silently crops subjects out of frame on tall ratios (r-3x4,r-21x9). - Thumbnail test. Downscale the rendered PNG to 360 px wide and confirm the title is still legible. If the title fights the photo, move the title, swap the photo, or add a localized image-toned tint; if the photo looks dead, the tint is too heavy or the photo was wrong for text-on-image.
Editorial dark covers (e.g. game journals on key art) and Swiss covers with hero photos both require these checks. Skipping them is a known failure mode (see style-system.md Anti-Pattern D).
When the user has no images:
- This branch only runs if the Step 1 "三选一" gate landed on B (web-sourced) or C (AI-generated). Never silently fall into B or C — the user picked one.
- For C (AI-generated): use generated bitmaps only where they add real value, usually 1-2 pages. Generate images that match the page's visual role, not generic decoration. Keep generated images free of embedded titles, page numbers, logos, or fake UI labels unless explicitly needed.
- For B (web-sourced): see the Web-Sourced Images section below.
Web-Sourced Images (fallback when user has none)
When the user has no screenshots/photos and a generated bitmap would not fit the page's role (e.g. Editorial atmosphere shot, outdoor / lifestyle backdrop, game cover art, real-world product shot), fetch from the web instead of leaving the page thin.
Policy: grab first, disclose after, let the user decide on attribution. Do not pre-filter sources by guessed license — the user is the rights holder of the final composition and decides what is acceptable.
Recommended sources, in order of preference. All five below are free-tier libraries with no required licensing fees; we do not pull from paid stock sites (视觉中国 / Getty / 站酷海洛 etc.).
1. Unsplash — https://unsplash.com/s/photos/<keyword>. Strong for outdoor / lifestyle / atmospheric backdrops. English keywords work best. License is permissive but verify case by case. 2. Pexels — https://www.pexels.com/search/<keyword>/ or https://www.pexels.com/zh-cn/search/<keyword>/. Supports Chinese keyword search natively — fills Unsplash's gap on 国内场景 (中文街景 / 国风物件 / 本地地名). Use this first when the subject is China-specific or the keyword is Chinese. Free under Pexels License. 3. Flickr CC-licensed pool — https://www.flickr.com/search/?text=<keyword>&license=2%2C3%2C4%2C5%2C6%2C9. The license filter (license=2,3,4,5,6,9) restricts to Creative Commons photos. Fills the "documentary realness" gap: street photography, people-in-context, real interiors, non-styled scenes that Unsplash/Pexels lack. Always preserve CC attribution if the user opts in. 4. Wallhaven — https://wallhaven.cc/search?q=<keyword>. Strong for game / anime / wallpaper themes. Content is user-uploaded, rights are unverified. 5. Direct web search — when a specific subject is needed (a product render, a game still, a historical photo). Use WebFetch / WebSearch to find a candidate URL.
Editorial-mode picking order: Pexels (if keyword is Chinese / China-specific) → Unsplash → Flickr CC (if you need real-life feel) → direct search. Swiss mode rarely needs any of these — product renders, UI screenshots, and keyshot-style images should be user-supplied or AI-generated, not stock.
How to fetch:
- Use WebFetch or
curlto download the image into the task folder'sassets/directory. - Name the file by purpose, not by hash:
assets/hero-mountain.jpg,assets/ui-pulse-card.png. - Record the source URL in a
assets/SOURCES.mdfile next to the images (one line per file:hero-mountain.jpg ← <url>). Always do this even if the user declines attribution in the final image — it preserves provenance for the human author.
After fetching, surface the provenance to the user before finalizing the design:
我从 <site> 取了这些图:
- assets/hero-mountain.jpg — <url>
- assets/ui-pulse-card.png — <url>
⚠️ 版权未经核实。请你判断是否可用。
是否需要在图文中标注来源?
- 要:我把 "Photo · <site> · @<author>" 加到对应页脚 / 角标。
- 不要:原样使用,不加注释。If the user picks "标注" — add a small mono caption (Swiss: .t-meta 18-20px in corner; Editorial: .label next to the image well). Never crowd the caption into the layout's focal area.
If the user picks "不标注" — proceed silently. The provenance still lives in assets/SOURCES.md for the user's own records.
If an image is only one element among many in a composite (e.g. one of nine photos in a matrix), the user may reasonably skip attribution. Do not force a credit label that breaks the layout.
7. Deliver
Show user first, validate on request. Auto-running the validator after every render takes too long and delays the user from seeing results. Default flow:
1. After rendering completes, immediately show the user the rendered images inline (absolute paths) with a one-sentence summary of what was built. 2. Ask one question: "先你自己看,还是我先自动核查一遍?" (Do you want to review first, or should I run the auto-check?) 3. If the user says "我自己看" / "先给我" / "no need" — stop here, let them inspect, and respond to whatever they raise. 4. If the user says "你查吧" / "auto-check" / "yes" — only then run node validate-social-deck.mjs <task-dir>, fix any FAIL, and re-render before final delivery. Mention density/cap WARNs.
Never silently run the validator before showing the user — it costs minutes per pass and the user often spots issues faster.
Final response (after the user has reviewed or asked for auto-check) should include:
- Output folder path.
- Rendered images shown inline with absolute paths when useful.
- A short note on dimensions and verification (or "not yet validated, awaiting your review").
- For any image fetched from the web: source URL + site + the attribution decision the user made.
- Any unresolved risks, such as source images being low resolution.
Non-Negotiables
- Never edit the original Guizang PPT skill or any upstream skill copied from elsewhere.
- Do not create random decorative SVG ovals, blobs, rain drops, stickers, or meaningless circles.
- Do not use nested cards or generic SaaS card layouts as the default.
- Do not let text overflow, touch the edge, or collide with the footer band. Pin
.footwithmargin-top: autoinside a flex column, never withposition: absoluteover growing content. - Do not let text become too small to read on mobile.
- Do not write inline
font-size+font-weighton display titles in Swiss. Use the typed classes (.h-hero/.h-statement/.h-xl/.num-mega). A 80-120px headline at weight 700-900 is not Swiss; "the larger, the lighter" is a hard rule. - Do not deliver Editorial posters with a flat paper background, mono labels on every row, and no atmosphere layer. Run the Editorial Identity Test in
references/style-system.md— a serif title alone does not make a poster Editorial. - Do not fake data, release details, or percentages.
- Do not crop faces, key UI text, or hardware/product details unless the user explicitly accepts it.
- Do not reuse a 21:9 cover by blindly cropping it into 1:1. Compose each ratio separately.
- 3:4 卡必须吃满画布。Content (text + image + data) 必须覆盖 ≥75% 画布高度。任何 >15% 画布高度的纯空白带都需要"留白理由":(a) hero image 自带呼吸、(b) 单句宣言式 hero statement、(c) 段落顶/底 leading & trailing whitespace(前后总和 ≤15%)。禁止用 `<div style="flex: 1"></div>` 上下夹击把内容塞到中段——杂志页留白逻辑不适用于社交卡(杂志靠对开页吸收留白,社交卡逐张独立刷,欠填看着像 PPT 漏排)。Recipe-by-recipe 最小密度见
references/layout-recipes.md每条 recipe 的「Minimum density」段。Render 后必须跑qa-checklist.md的 4 横带密度检查。
# Local test workspace — demos, screenshots, downloaded assets, debug scripts.
# Skill iterates only on SKILL.md / references/ / assets/ / agents/ / validate.
local-tests/
# Node + macOS
node_modules/
.DS_Store
*.log
npm-debug.log*
# Editor / OS
.vscode/
.idea/
interface:
display_name: "Guizang Social Card Skill"
short_description: "Create social card sets and WeChat covers."
brand_color: "#002FA7"
default_prompt: "Use $guizang-social-card-skill to turn this article into a 3:4 social card image set and a paired WeChat 21:9 + 1:1 cover."
policy:
allow_implicit_invocation: true
/* Reusable Guizang-style electronic magazine background.
Copy or inline into generated HTML. It renders a deterministic WebGL ink-flow
canvas and falls back to 2D if WebGL is unavailable. */
(function () {
function rgb(input, fallback) {
return Array.isArray(input) && input.length === 3 ? input.map((v) => v / 255) : fallback;
}
function fallback2d(canvas, opts) {
const ctx = canvas.getContext("2d");
if (!ctx) return;
const dpr = Math.min(2, window.devicePixelRatio || 1);
const rect = canvas.getBoundingClientRect();
canvas.width = Math.max(1, Math.floor(rect.width * dpr));
canvas.height = Math.max(1, Math.floor(rect.height * dpr));
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
const w = rect.width;
const h = rect.height;
const ink = opts.inkCss || "rgba(26,46,31,.22)";
const accent = opts.accentCss || "rgba(46,107,79,.18)";
ctx.clearRect(0, 0, w, h);
for (let i = 0; i < 5; i++) {
const x = w * (0.14 + ((i * 0.19 + 0.17) % 0.72));
const y = h * (0.10 + ((i * 0.29 + 0.23) % 0.78));
const r = Math.max(w, h) * (0.22 + i * 0.045);
const g = ctx.createRadialGradient(x, y, 0, x, y, r);
g.addColorStop(0, i % 2 ? accent : ink);
g.addColorStop(1, "rgba(0,0,0,0)");
ctx.fillStyle = g;
ctx.fillRect(0, 0, w, h);
}
ctx.globalAlpha = 0.09;
ctx.strokeStyle = opts.inkCss || "rgba(26,46,31,.22)";
for (let i = 0; i < 18; i++) {
ctx.beginPath();
ctx.ellipse(w * (0.5 + Math.sin(i) * 0.34), h * (0.5 + Math.cos(i * 1.7) * 0.28), 120 + i * 18, 42 + i * 9, i * 0.37, 0, Math.PI * 2);
ctx.stroke();
}
ctx.globalAlpha = 1;
}
function mount(canvas, options) {
const opts = options || {};
const gl = canvas.getContext("webgl", { alpha: true, antialias: true, preserveDrawingBuffer: true });
if (!gl) {
fallback2d(canvas, opts);
return { mode: "2d" };
}
const vert = `
attribute vec2 aPos;
void main(){ gl_Position = vec4(aPos, 0.0, 1.0); }
`;
const frag = `
precision mediump float;
uniform vec2 uRes;
uniform float uTime;
uniform vec3 uInk;
uniform vec3 uPaper;
uniform vec3 uAccent;
uniform float uStrength;
float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }
float noise(vec2 p){
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(
mix(hash(i + vec2(0.0,0.0)), hash(i + vec2(1.0,0.0)), u.x),
mix(hash(i + vec2(0.0,1.0)), hash(i + vec2(1.0,1.0)), u.x),
u.y
);
}
float fbm(vec2 p){
float v = 0.0;
float a = 0.5;
mat2 m = mat2(1.6, 1.2, -1.2, 1.6);
for(int i=0; i<5; i++){
v += a * noise(p);
p = m * p * 1.18;
a *= 0.52;
}
return v;
}
void main(){
vec2 uv = gl_FragCoord.xy / uRes.xy;
vec2 p = (gl_FragCoord.xy - 0.5 * uRes.xy) / min(uRes.x, uRes.y);
float t = uTime * 0.045;
float n = fbm(p * 2.2 + vec2(t, -t * 0.7));
float n2 = fbm(p * 5.4 - vec2(t * 1.7, t));
float contour = smoothstep(0.015, 0.0, abs(fract((n + n2 * 0.35) * 7.0) - 0.5));
float wash = smoothstep(0.22, 0.88, n);
float vignette = 1.0 - smoothstep(0.28, 0.92, length(p));
vec3 col = mix(uPaper, uInk, wash * 0.42 + contour * 0.18);
col = mix(col, uAccent, (0.18 + 0.25 * n2) * contour);
float alpha = uStrength * (0.18 + wash * 0.42 + contour * 0.30) * (0.55 + 0.45 * vignette);
gl_FragColor = vec4(col, alpha);
}
`;
function shader(type, source) {
const sh = gl.createShader(type);
gl.shaderSource(sh, source);
gl.compileShader(sh);
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(sh));
return sh;
}
const program = gl.createProgram();
gl.attachShader(program, shader(gl.VERTEX_SHADER, vert));
gl.attachShader(program, shader(gl.FRAGMENT_SHADER, frag));
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(program));
gl.useProgram(program);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, -1,1, 1,-1, 1,1]), gl.STATIC_DRAW);
const loc = gl.getAttribLocation(program, "aPos");
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
const uRes = gl.getUniformLocation(program, "uRes");
const uTime = gl.getUniformLocation(program, "uTime");
const uInk = gl.getUniformLocation(program, "uInk");
const uPaper = gl.getUniformLocation(program, "uPaper");
const uAccent = gl.getUniformLocation(program, "uAccent");
const uStrength = gl.getUniformLocation(program, "uStrength");
const ink = rgb(opts.ink, [0.10, 0.18, 0.12]);
const paper = rgb(opts.paper, [0.96, 0.94, 0.90]);
const accent = rgb(opts.accent, [0.18, 0.42, 0.31]);
function resize() {
const dpr = Math.min(2, window.devicePixelRatio || 1);
const rect = canvas.getBoundingClientRect();
canvas.width = Math.max(1, Math.floor(rect.width * dpr));
canvas.height = Math.max(1, Math.floor(rect.height * dpr));
gl.viewport(0, 0, canvas.width, canvas.height);
}
function draw(time) {
resize();
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.uniform2f(uRes, canvas.width, canvas.height);
gl.uniform1f(uTime, opts.frozenTime == null ? time * 0.001 : opts.frozenTime);
gl.uniform3f(uInk, ink[0], ink[1], ink[2]);
gl.uniform3f(uPaper, paper[0], paper[1], paper[2]);
gl.uniform3f(uAccent, accent[0], accent[1], accent[2]);
gl.uniform1f(uStrength, opts.strength == null ? 0.34 : opts.strength);
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
draw(0);
if (opts.frozenTime == null) requestAnimationFrame(draw);
return { mode: "webgl", redraw: () => draw(0) };
}
window.MagazineBg = { mount };
})();
<!doctype html>
<html lang="zh-CN" data-theme="ink-classic">
<head>
<meta charset="utf-8">
<title>[必填] 替换为社交卡组标题 · Social Card Set</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;700;900&family=Noto+Sans+SC:wght@300;400;500;700&family=Playfair+Display:ital,wght@0,400;0,700;1,400&family=Inter:wght@300;400;500;700&family=IBM+Plex+Mono:wght@400;500&display=swap">
<style>
/* =========================================================
Editorial Magazine × E-ink — Social Card Seed Template
---------------------------------------------------------
Three boards built in one HTML:
.poster.xhs 1080×1440 (3:4 小红书)
.poster.square 1080×1080 (1:1 微信方封面)
.poster.wide 2100× 900 (21:9 微信主封面)
.pair-preview 21:9 + 1:1 配对预览
Theme switching via <html data-theme="…">:
ink-classic | indigo-porcelain | forest-ink | kraft-paper | dune | midnight-ink
Fill posters where marked <!-- POSTERS_HERE -->.
Each poster carries one Layout Recipe (M01-M15) from
references/layout-recipes.md.
========================================================= */
/* ---------- 1. Theme tokens ---------- */
:root,
[data-theme="ink-classic"] {
--paper: #f3f0e8;
--paper-2: #ebe6da;
--ink: #0a0a0b;
--muted: #68625a;
--line: rgba(10,10,11,.22);
--accent: #111111;
--accent-soft: #d8d2c6;
--ink-rgb: 10,10,11;
--paper-rgb: 243,240,232;
--accent-rgb: 17,17,17;
}
[data-theme="indigo-porcelain"] {
--paper: #f2f4f5;
--paper-2: #e5ebef;
--ink: #0a1f3d;
--muted: #5f6d78;
--line: rgba(10,31,61,.20);
--accent: #315d93;
--accent-soft: #d7e1ec;
--ink-rgb: 10,31,61;
--paper-rgb: 242,244,245;
--accent-rgb: 49,93,147;
}
[data-theme="forest-ink"] {
--paper: #f5f1e8;
--paper-2: #e8dfcf;
--ink: #16251b;
--muted: #5d665d;
--line: rgba(22,37,27,.22);
--accent: #2e6b4f;
--accent-soft: #d4dfd2;
--ink-rgb: 22,37,27;
--paper-rgb: 245,241,232;
--accent-rgb: 46,107,79;
}
[data-theme="kraft-paper"] {
--paper: #eedfc7;
--paper-2: #dfc9a8;
--ink: #2a1e13;
--muted: #755f49;
--line: rgba(42,30,19,.24);
--accent: #9b5a2e;
--accent-soft: #d5b58f;
--ink-rgb: 42,30,19;
--paper-rgb: 238,223,199;
--accent-rgb: 155,90,46;
}
[data-theme="dune"] {
--paper: #f0e6d2;
--paper-2: #ded0b7;
--ink: #1f1a14;
--muted: #6f6557;
--line: rgba(31,26,20,.22);
--accent: #8f7650;
--accent-soft: #d4c2a4;
--ink-rgb: 31,26,20;
--paper-rgb: 240,230,210;
--accent-rgb: 143,118,80;
}
/* Midnight Ink — the ONLY official dark Editorial palette.
Use for game key art, night scenes, dark photography, cinematic covers.
Do not improvise other dark backgrounds. */
[data-theme="midnight-ink"] {
--paper: #0e0d0c;
--paper-2: #1a1714;
--ink: #ece2cf;
--muted: #9a8c75;
--line: rgba(236,226,207,.22);
--accent: #d4a04a;
--accent-soft: #3a2a14;
--ink-rgb: 236,226,207;
--paper-rgb: 14,13,12;
--accent-rgb: 212,160,74;
}
/* ---------- 2. Typography stack ---------- */
:root {
--serif-zh: "Noto Serif SC", "Songti SC", "STSong", serif;
--serif-en: "Playfair Display", "Noto Serif SC", serif;
--sans-zh: "Noto Sans SC", -apple-system, "PingFang SC", "Microsoft YaHei UI", sans-serif;
--sans-en: "Inter", -apple-system, BlinkMacSystemFont, sans-serif;
--mono: "IBM Plex Mono", ui-monospace, "SF Mono", Consolas, monospace;
}
/* ---------- 3. Reset & sheet ---------- */
*,*::before,*::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
background: #1a1a1a;
font-family: var(--sans-zh);
color: var(--ink);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
padding: 64px 32px;
}
.sheet {
display: flex;
flex-direction: column;
align-items: center;
gap: 48px;
}
/* ---------- 4. Poster boards (fixed export sizes) ---------- */
.poster {
position: relative;
background: var(--paper);
color: var(--ink);
overflow: hidden;
isolation: isolate;
}
.poster.xhs { width: 1080px; height: 1440px; }
.poster.square { width: 1080px; height: 1080px; }
.poster.wide { width: 2100px; height: 900px; }
.poster figure { margin: 0; }
/* Default content padding by board */
.poster.xhs .content { padding: 96px 88px; }
.poster.square .content { padding: 88px 88px; }
.poster.wide .content { padding: 88px 120px; }
.content { position: relative; width: 100%; height: 100%; z-index: 2; }
/* ---------- 5. Background layers (paper grain + WebGL) ---------- */
.mag-bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
z-index: 0;
pointer-events: none;
}
.grain {
position: absolute;
inset: 0;
z-index: 1;
pointer-events: none;
opacity: .35;
mix-blend-mode: multiply;
background-image: radial-gradient(rgba(0,0,0,.045) 1px, transparent 1px);
background-size: 3px 3px;
}
.paper-wash {
position: absolute;
inset: 0;
z-index: 1;
pointer-events: none;
background:
linear-gradient(180deg, rgba(var(--ink-rgb),.02), rgba(var(--ink-rgb),.05) 60%, rgba(var(--ink-rgb),.08));
}
/* Midnight Ink — flip grain to screen-mode warm specks, paper-wash to
gilt + shadow vignette. Light-paper math doesn't carry over. */
[data-theme="midnight-ink"] .grain {
opacity: .26;
mix-blend-mode: screen;
background-image: radial-gradient(rgba(255,244,214,.10) 1px, transparent 1px);
}
[data-theme="midnight-ink"] .paper-wash {
background:
radial-gradient(80% 50% at 28% 16%, rgba(212,160,74,.12), transparent 64%),
radial-gradient(70% 60% at 80% 86%, rgba(60,40,20,.20), transparent 72%),
linear-gradient(180deg, rgba(236,226,207,.02), rgba(0,0,0,.32));
}
/* ---------- 6. Typography roles ----------
Editorial Magazine values restored from demo-showcase/editorial.html:
light weights (500 not 900), serif body italic-tolerant, wide tracking
on kicker/meta (.20-.22em), positive tracking on display (+.04em).
Each card should still INLINE-tune font-size for the specific recipe;
these defaults are intentionally restrained, not maximal. */
.kicker {
font-family: var(--mono);
font-size: 21px;
letter-spacing: .22em;
text-transform: uppercase;
color: rgba(var(--ink-rgb), .55);
margin: 0 0 18px;
}
.h-display {
font-family: var(--serif-zh);
font-weight: 500;
font-size: 124px;
line-height: 1.06;
letter-spacing: .04em;
margin: 0 0 24px;
color: var(--ink);
}
.h-xl {
font-family: var(--serif-zh);
font-weight: 500;
font-size: 88px;
line-height: 1.10;
letter-spacing: .03em;
margin: 0 0 24px;
color: var(--ink);
}
.h-md {
font-family: var(--serif-zh);
font-weight: 500;
font-size: 56px;
line-height: 1.18;
letter-spacing: .02em;
margin: 0 0 18px;
color: var(--ink);
}
.h-sub {
font-family: var(--serif-en);
font-style: italic;
font-weight: 400;
font-size: 36px;
color: var(--muted);
margin: 0 0 24px;
}
.lead {
font-family: var(--serif-zh);
font-weight: 400;
font-size: 28px;
line-height: 1.55;
color: rgba(var(--ink-rgb), .82);
margin: 0 0 24px;
}
.body {
font-family: var(--serif-zh);
font-weight: 400;
font-size: 24px;
line-height: 1.65;
color: rgba(var(--ink-rgb), .80);
margin: 0 0 18px;
}
.meta {
font-family: var(--mono);
font-size: 18px;
letter-spacing: .20em;
text-transform: uppercase;
color: rgba(var(--ink-rgb), .55);
}
.label {
font-family: var(--mono);
font-size: 18px;
letter-spacing: .20em;
text-transform: uppercase;
color: rgba(var(--ink-rgb), .55);
}
.pullquote {
font-family: var(--serif-zh);
font-style: italic;
font-weight: 500;
font-size: 64px;
line-height: 1.28;
color: var(--ink);
margin: 0;
}
/* Square boards usually need tighter scales */
.poster.square .h-display { font-size: 116px; }
.poster.square .h-xl { font-size: 78px; }
.poster.square .h-md { font-size: 48px; }
.poster.square .lead { font-size: 28px; }
.poster.square .body { font-size: 26px; }
/* Wide boards have horizontal headlines */
.poster.wide .h-display { font-size: 128px; }
.poster.wide .h-xl { font-size: 96px; }
.poster.wide .lead { font-size: 34px; }
/* ---------- 7. Issue label + page metadata ---------- */
.issue-row {
display: flex;
align-items: center;
gap: 16px;
font-family: var(--mono);
font-size: 20px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--muted);
}
.issue-row .dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent);
}
.issue-strip {
position: absolute;
left: 88px;
right: 88px;
bottom: 56px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 32px;
font-family: var(--mono);
font-size: 18px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--muted);
border-top: 1px solid var(--line);
padding-top: 18px;
}
.poster.wide .issue-strip { left: 120px; right: 120px; }
.issue-strip span { white-space: nowrap; }
/* ---------- 8. Layout primitives ---------- */
.stack { display: flex; flex-direction: column; }
.gap-1 { gap: 12px; }
.gap-2 { gap: 24px; }
.gap-3 { gap: 36px; }
.gap-4 { gap: 48px; }
.gap-5 { gap: 64px; }
.row { display: flex; flex-direction: row; }
.col-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 48px; }
.col-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 36px; }
.col-2-7-5 { display: grid; grid-template-columns: 7fr 5fr; gap: 48px; }
.col-2-8-4 { display: grid; grid-template-columns: 8fr 4fr; gap: 48px; }
.center { text-align: center; }
.grow { flex: 1; }
.pad-y-md { padding-top: 36px; padding-bottom: 36px; }
.rule { height: 1px; background: var(--line); border: 0; margin: 24px 0; }
.rule-accent { height: 2px; background: var(--accent); border: 0; margin: 24px 0; width: 96px; }
/* ---------- 9. Image containers (standard ratios) ---------- */
.frame-img {
position: relative;
overflow: hidden;
background: var(--paper-2);
display: block;
margin: 0;
}
.frame-img img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center 50%;
display: block;
}
.frame-img.fit-contain img { object-fit: contain; }
[data-theme="midnight-ink"] .frame-img {
background: #18120f;
box-shadow: 0 0 0 1px rgba(236,226,207,.10);
}
.frame-img.r-3x4 { aspect-ratio: 3 / 4; }
.frame-img.r-1x1 { aspect-ratio: 1 / 1; }
.frame-img.r-4x3 { aspect-ratio: 4 / 3; }
.frame-img.r-16x9 { aspect-ratio: 16 / 9; }
.frame-img.r-16x10 { aspect-ratio: 16 / 10; }
.frame-img.r-21x9 { aspect-ratio: 21 / 9; }
.frame-img.r-3x2 { aspect-ratio: 3 / 2; }
.img-cap {
font-family: var(--mono);
font-size: 20px;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--muted);
margin-top: 14px;
}
/* ---------- 9b. Screenshot treatment (.frame-shot) ---------- */
/* Use for UI screenshots / web captures / code shots. Replaces .frame-img
when you need rounded corners, soft shadow, padded inset, or device chrome.
Stack: .frame-shot.r-{ratio}.bg-{tone}.inset-{none|sub|bal}.shadow-{none|soft|ed} */
.frame-shot {
position: relative;
overflow: hidden;
display: block;
margin: 0;
background: var(--paper-2);
/* default corners — Editorial leans slight rounding to feel like a magazine cutout */
border-radius: 6px;
}
.frame-shot.corners-sq { border-radius: 0; }
.frame-shot.corners-sm { border-radius: 6px; }
.frame-shot.corners-md { border-radius: 14px; }
/* shadows — Editorial uses warm low-contrast drop */
.frame-shot.shadow-soft { box-shadow: 0 18px 36px -18px rgba(20,18,14,.22), 0 2px 6px rgba(20,18,14,.06); }
.frame-shot.shadow-ed { box-shadow: 0 28px 60px -24px rgba(20,18,14,.32), 0 6px 14px rgba(20,18,14,.08); }
/* background tones — programmatic, no asset deps */
.frame-shot.bg-paper { background: var(--paper); }
.frame-shot.bg-paper-2 { background: var(--paper-2); }
.frame-shot.bg-ink { background: var(--ink); }
.frame-shot.bg-grid {
background:
linear-gradient(var(--paper-2), var(--paper-2)),
repeating-linear-gradient(0deg, var(--line) 0 1px, transparent 1px 48px),
repeating-linear-gradient(90deg, var(--line) 0 1px, transparent 1px 48px);
background-blend-mode: multiply;
}
.frame-shot.bg-dot {
background-color: var(--paper-2);
background-image: radial-gradient(var(--line) 1px, transparent 1px);
background-size: 18px 18px;
}
/* asset backgrounds — ported from PPT skill, real texture for screenshot wells */
/* path is relative to the deck's index.html — keep seed and final deck in same dir, or override */
.frame-shot.bg-asset-dune { background: #d8c9a8 url("../assets/screenshot-backgrounds/style-a/dune.webp") center/cover no-repeat; }
.frame-shot.bg-asset-forest-ink { background: #1f2a24 url("../assets/screenshot-backgrounds/style-a/forest-ink.webp") center/cover no-repeat; }
.frame-shot.bg-asset-indigo-porcelain { background: #cfd6dd url("../assets/screenshot-backgrounds/style-a/indigo-porcelain.webp") center/cover no-repeat; }
.frame-shot.bg-asset-kraft-paper { background: #c9a983 url("../assets/screenshot-backgrounds/style-a/kraft-paper.webp") center/cover no-repeat; }
.frame-shot.bg-asset-monocle-classic { background: #e8e1d4 url("../assets/screenshot-backgrounds/style-a/monocle-classic.webp") center/cover no-repeat; }
/* padding wraps the inner shot — content gets to "float" on the bg tone */
.frame-shot.inset-none > img,
.frame-shot.inset-none > .shot-body { padding: 0; }
.frame-shot.inset-sub { padding: 24px; }
.frame-shot.inset-bal { padding: 56px; }
/* aspect-ratio reused from .frame-img */
.frame-shot.r-3x4 { aspect-ratio: 3 / 4; }
.frame-shot.r-1x1 { aspect-ratio: 1 / 1; }
.frame-shot.r-4x3 { aspect-ratio: 4 / 3; }
.frame-shot.r-16x9 { aspect-ratio: 16 / 9; }
.frame-shot.r-16x10 { aspect-ratio: 16 / 10; }
.frame-shot.r-21x9 { aspect-ratio: 21 / 9; }
.frame-shot.r-3x2 { aspect-ratio: 3 / 2; }
/* inner img defaults to contain (screenshots must not crop UI) */
.frame-shot > img {
width: 100%;
height: 100%;
object-fit: contain;
object-position: center;
display: block;
border-radius: inherit;
}
.frame-shot.fit-cover > img { object-fit: cover; }
/* device-frame variants (CSS-only chrome, no SVG / images) */
.device-browser {
position: relative;
background: var(--paper);
border: 1px solid var(--line);
border-radius: 10px;
overflow: hidden;
}
.device-browser::before {
content: '';
display: block;
height: 36px;
background: var(--paper-2);
border-bottom: 1px solid var(--line);
background-image:
radial-gradient(circle at 18px 18px, #f08080 5px, transparent 5.5px),
radial-gradient(circle at 38px 18px, #f5c450 5px, transparent 5.5px),
radial-gradient(circle at 58px 18px, #7ec98f 5px, transparent 5.5px);
}
.device-browser > .frame-shot,
.device-browser > img {
border-radius: 0;
display: block;
width: 100%;
}
.device-phone {
position: relative;
background: var(--ink);
border-radius: 36px;
padding: 14px;
box-shadow: 0 24px 48px -20px rgba(20,18,14,.42);
}
.device-phone > .frame-shot,
.device-phone > img {
border-radius: 24px;
display: block;
width: 100%;
}
/* ---------- 9c. Map block (.map-block) ---------- */
/* Static map container — pure CSS frame, content is either an <img> from
Mapbox Static API or an SVG schematic. Pins overlay as absolute children. */
.map-block {
position: relative;
background: var(--paper-2);
overflow: hidden;
aspect-ratio: 4 / 3;
width: 100%;
}
.map-block.r-16x10 { aspect-ratio: 16 / 10; }
.map-block.r-16x9 { aspect-ratio: 16 / 9; }
.map-block.r-1x1 { aspect-ratio: 1 / 1; }
.map-block.r-3x4 { aspect-ratio: 3 / 4; }
.map-block > img,
.map-block > svg {
position: absolute; inset: 0;
width: 100%; height: 100%;
display: block;
}
.map-block > img { object-fit: cover; }
/* tint mapbox / OSM tiles to fit editorial paper tone */
.map-block.tone-paper > img { filter: saturate(.36) contrast(.92) brightness(1.04); mix-blend-mode: multiply; }
.map-block.tone-ink > img { filter: saturate(.18) brightness(.62); }
/* schematic mode: SVG drawn at viewBox 0 0 100 100, lon/lat pre-mapped to 0-100 */
.map-block > svg .map-coast { fill: none; stroke: var(--line); stroke-width: .4; }
.map-block > svg .map-road { fill: none; stroke: var(--muted); stroke-width: .28; stroke-dasharray: 1.2 .8; opacity: .65; }
.map-block > svg .map-water { fill: var(--paper-2); }
.map-block > svg .map-grid { stroke: var(--line); stroke-width: .15; opacity: .55; }
/* pins overlay — positioned by % coordinates */
.map-pin {
position: absolute;
width: 0; height: 0;
transform: translate(-50%, -50%);
}
.map-pin .dot {
position: absolute;
left: -7px; top: -7px;
width: 14px; height: 14px;
border-radius: 50%;
background: var(--ink);
border: 2px solid var(--paper);
box-shadow: 0 1px 4px rgba(20,18,14,.32);
}
.map-pin.accent .dot { background: var(--accent); }
.map-pin .line {
position: absolute;
left: 8px; top: 0;
width: 32px; height: 1px;
background: var(--ink);
opacity: .5;
}
.map-pin.left .line { left: auto; right: 8px; }
.map-pin .card {
position: absolute;
left: 44px; top: -22px;
min-width: 86px;
padding: 8px 10px;
background: rgba(245,241,232,.94);
box-shadow: 0 0 0 1px var(--line);
border-radius: 2px;
white-space: nowrap;
}
.map-pin.left .card { left: auto; right: 44px; }
.map-pin .card .name {
font-family: var(--serif-zh);
font-size: 16px;
line-height: 1.1;
color: var(--ink);
}
.map-pin .card .meta {
display: block;
margin-top: 4px;
font-family: var(--mono);
font-size: 10px;
letter-spacing: .14em;
text-transform: uppercase;
color: var(--muted);
}
.map-pin.accent .card .name { color: var(--accent); }
.map-legend {
position: absolute;
left: 14px; bottom: 14px;
z-index: 3;
background: rgba(245,241,232,.92);
padding: 8px 12px;
box-shadow: 0 0 0 1px var(--line);
font-family: var(--mono);
font-size: 11px;
letter-spacing: .14em;
text-transform: uppercase;
color: var(--muted);
}
.map-legend.tr { left: auto; right: 14px; top: 14px; bottom: auto; }
/* ---------- 10. Editorial components ---------- */
.callout {
border-left: 3px solid var(--accent);
padding: 18px 28px;
font-family: var(--serif-zh);
font-style: italic;
font-size: 36px;
line-height: 1.35;
color: var(--ink);
background: var(--paper-2);
}
.callout .callout-src {
display: block;
margin-top: 18px;
font-style: normal;
font-family: var(--mono);
font-size: 20px;
letter-spacing: .12em;
color: var(--muted);
}
.ledger { display: flex; flex-direction: column; }
.ledger-row {
display: grid;
grid-template-columns: 96px 1fr auto;
gap: 24px;
align-items: baseline;
padding: 28px 0;
border-bottom: 1px solid var(--line);
}
.ledger-row .ledger-nb {
font-family: var(--mono);
font-size: 28px;
color: var(--accent);
letter-spacing: .08em;
}
.ledger-row .ledger-title {
font-family: var(--serif-zh);
font-weight: 500;
font-size: 42px;
letter-spacing: .02em;
color: var(--ink);
}
.ledger-row .ledger-note {
font-family: var(--serif-zh);
font-weight: 400;
font-size: 22px;
line-height: 1.55;
color: rgba(var(--ink-rgb), .72);
}
.marginalia {
display: grid;
grid-template-columns: 1fr 220px;
gap: 48px;
}
.marginalia .mg-col {
border-left: 1px solid var(--line);
padding-left: 24px;
}
.marginalia .mg-col p {
font-family: var(--mono);
font-size: 20px;
letter-spacing: .04em;
color: var(--muted);
margin: 0 0 16px;
}
.pipeline-v { display: flex; flex-direction: column; gap: 28px; }
.pipeline-v .step {
display: grid;
grid-template-columns: 80px 1fr;
gap: 28px;
align-items: baseline;
padding-bottom: 24px;
border-bottom: 1px solid var(--line);
}
.pipeline-v .step:last-child { border-bottom: 0; }
.pipeline-v .step-nb {
font-family: var(--mono);
font-size: 32px;
color: var(--accent);
}
.pipeline-v .step-title {
font-family: var(--serif-zh);
font-weight: 500;
font-size: 40px;
letter-spacing: .02em;
margin: 0 0 8px;
}
.pipeline-v .step-desc {
font-family: var(--serif-zh);
font-weight: 400;
font-size: 24px;
line-height: 1.6;
color: rgba(var(--ink-rgb), .72);
margin: 0;
}
.beforeafter {
display: grid;
grid-template-rows: 1fr 1fr;
gap: 32px;
height: 100%;
}
.beforeafter .ba-block {
padding: 32px;
background: var(--paper-2);
border-left: 3px solid var(--accent);
}
.beforeafter .ba-block.before { opacity: .68; }
/* ---------- 11. Pair preview frame ---------- */
.pair-preview {
width: 2400px;
min-height: 1180px;
background: var(--paper-2);
padding: 48px;
display: grid;
grid-template-columns: 2100px 1080px;
grid-template-rows: 900px 1080px;
gap: 48px;
align-content: center;
justify-content: center;
}
.pair-preview .preview-wide { grid-column: 1; grid-row: 1 / span 2; align-self: center; }
.pair-preview .preview-square { grid-column: 2; grid-row: 1 / span 2; align-self: center; }
.pair-preview .preview-label {
font-family: var(--mono);
font-size: 20px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 14px;
}
/* ---------- 12. Per-poster mounting helpers ---------- */
.poster .corner-tl,
.poster .corner-tr,
.poster .corner-bl,
.poster .corner-br {
position: absolute;
font-family: var(--mono);
font-size: 18px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--muted);
z-index: 3;
}
.poster.xhs .corner-tl,
.poster.square .corner-tl { left: 88px; top: 56px; }
.poster.xhs .corner-tr,
.poster.square .corner-tr { right: 88px; top: 56px; }
.poster.xhs .corner-bl,
.poster.square .corner-bl { left: 88px; bottom: 56px; }
.poster.xhs .corner-br,
.poster.square .corner-br { right: 88px; bottom: 56px; }
.poster.wide .corner-tl { left: 120px; top: 56px; }
.poster.wide .corner-tr { right: 120px; top: 56px; }
.poster.wide .corner-bl { left: 120px; bottom: 56px; }
.poster.wide .corner-br { right: 120px; bottom: 56px; }
</style>
</head>
<body>
<!-- THEME_OVERRIDE
Change <html data-theme="…"> above to:
ink-classic | indigo-porcelain | forest-ink | kraft-paper | dune | midnight-ink
-->
<main class="sheet">
<!-- POSTERS_HERE -->
<!--
Replace the placeholder posters below with real content.
Each poster carries one Layout Recipe (M01-M15) from
references/layout-recipes.md.
Available board sizes:
<section class="poster xhs" id="xhs-01">…</section> 3:4
<section class="poster square" id="wechat-1x1">…</section> 1:1
<section class="poster wide" id="wechat-21x9">…</section> 21:9
For WeChat covers, also include a <section class="pair-preview">
that places the 21:9 + 1:1 side-by-side for review.
-->
<!-- ============================================================
Placeholder: M01 Magazine Issue Cover (3:4)
Replace with real cover content from layout-recipes.md.
============================================================ -->
<section class="poster xhs" id="xhs-01">
<canvas class="mag-bg" data-bg="ink-flow"></canvas>
<div class="grain"></div>
<div class="content stack gap-4">
<div class="issue-row">
<span>Vol. 01</span><span class="dot"></span><span>2026.05</span>
</div>
<div class="stack gap-2">
<p class="kicker">封面 · Cover</p>
<h1 class="h-display">[标题占位]<br>替换为真实文案</h1>
<p class="h-sub">A working subtitle</p>
</div>
<figure class="frame-img r-16x10">
<img src="assets/cover.jpg" alt="cover image placeholder">
</figure>
<p class="lead">用一段克制的引语解释为什么这一组卡片值得读完。</p>
</div>
<div class="issue-strip">
<span>Issue · Magazine</span>
<span>—</span>
<span>by [作者]</span>
</div>
</section>
</main>
<!-- Magazine WebGL background (deterministic ink-flow) -->
<script src="assets/magazine-bg-webgl.js"></script>
<script>
(function () {
function cssVarRgb(name, fallback) {
var raw = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
if (!raw) return fallback;
var parts = raw.split(',').map(function (x) { return parseInt(x.trim(), 10); });
return parts.length === 3 && parts.every(function (n) { return !isNaN(n); }) ? parts : fallback;
}
function mountAll() {
if (!window.MagazineBg || !window.MagazineBg.mount) return;
var ink = cssVarRgb('--ink-rgb', [10, 10, 11]);
var paper = cssVarRgb('--paper-rgb', [243, 240, 232]);
var accent = cssVarRgb('--accent-rgb', [17, 17, 17]);
document.querySelectorAll('canvas.mag-bg').forEach(function (c) {
window.MagazineBg.mount(c, {
ink: ink,
paper: paper,
accent: accent,
strength: 0.32,
frozenTime: 12.5
});
});
}
if (document.readyState === 'complete') mountAll();
else window.addEventListener('load', mountAll);
})();
</script>
</body>
</html>
<!doctype html>
<html lang="zh-CN" data-accent="ikb">
<head>
<meta charset="utf-8">
<title>[必填] 替换为社交卡组标题 · Social Card Set</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700&family=Noto+Sans+SC:wght@200;300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap">
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
<style>
/* =========================================================
Swiss International — Social Card Seed Template
---------------------------------------------------------
Three boards built in one HTML:
.poster.xhs 1080×1440 (3:4 小红书)
.poster.square 1080×1080 (1:1 微信方封面)
.poster.wide 2100× 900 (21:9 微信主封面)
.pair-preview 21:9 + 1:1 配对预览
Accent switching via <html data-accent="…">:
ikb (Klein Blue) | lemon-yellow | lemon-green | safety-orange
Fill posters where marked <!-- POSTERS_HERE -->.
Each poster carries one Layout Recipe (S01-S12) from
references/layout-recipes.md.
========================================================= */
/* ---------- 1. Accent palettes (4 mutually exclusive) ---------- */
:root,
[data-accent="ikb"] {
--paper: #fafaf8;
--ink: #0a0a0a;
--grey-1: #f0f0ee;
--grey-2: #d4d4d2;
--grey-3: #737373;
--accent: #002FA7;
--accent-on: #ffffff;
}
[data-accent="lemon-yellow"] {
--paper: #fafaf8;
--ink: #0a0a0a;
--grey-1: #f0f0ee;
--grey-2: #d4d4d2;
--grey-3: #737373;
--accent: #FFD500;
--accent-on: #0a0a0a;
}
[data-accent="lemon-green"] {
--paper: #fafaf8;
--ink: #0a0a0a;
--grey-1: #f0f0ee;
--grey-2: #d4d4d2;
--grey-3: #737373;
--accent: #C5E803;
--accent-on: #0a0a0a;
}
[data-accent="safety-orange"] {
--paper: #fafaf8;
--ink: #0a0a0a;
--grey-1: #f0f0ee;
--grey-2: #d4d4d2;
--grey-3: #737373;
--accent: #FF6B35;
--accent-on: #ffffff;
}
/* ---------- 2. Typography stack ---------- */
:root {
--sans: "Inter", "Helvetica Neue", Helvetica, "Noto Sans SC",
-apple-system, "PingFang SC", "Microsoft YaHei UI", sans-serif;
--sans-zh: "Noto Sans SC", -apple-system, "PingFang SC",
"Microsoft YaHei UI", "Inter", sans-serif;
--mono: "IBM Plex Mono", ui-monospace, "SF Mono", Consolas, monospace;
}
/* ---------- 3. Spacing tokens (8/12/16/24/32/40/48/64/80/96/160) ---------- */
:root {
--sp-3: 8px;
--sp-4: 12px;
--sp-5: 16px;
--sp-6: 24px;
--sp-7: 32px;
--sp-8: 40px;
--sp-9: 48px;
--sp-10: 64px;
--sp-11: 80px;
--sp-12: 96px;
--sp-13:160px;
}
/* ---------- 4. Reset & sheet ---------- */
*,*::before,*::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
background: #1a1a1a;
font-family: var(--sans);
color: var(--ink);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
padding: 64px 32px;
}
.sheet {
display: flex;
flex-direction: column;
align-items: center;
gap: 48px;
}
/* ---------- 5. Poster boards (fixed export sizes) ---------- */
.poster {
position: relative;
background: var(--paper);
color: var(--ink);
overflow: hidden;
isolation: isolate;
}
.poster.xhs { width: 1080px; height: 1440px; }
.poster.square { width: 1080px; height: 1080px; }
.poster.wide { width: 2100px; height: 900px; }
.poster figure { margin: 0; }
.poster.xhs .content { padding: var(--sp-12) var(--sp-11); }
.poster.square .content { padding: var(--sp-11) var(--sp-11); }
.poster.wide .content { padding: var(--sp-11) var(--sp-13); }
.content { position: relative; width: 100%; height: 100%; z-index: 2; }
/* Optional decorative layers — opt in per poster */
.dot-mat,
.ring-mat,
.cross-mat {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 1;
}
.dot-mat {
opacity: .08;
background-image: radial-gradient(var(--ink) 1.5px, transparent 1.5px);
background-size: 24px 24px;
}
.ring-mat {
opacity: .08;
background-image: radial-gradient(circle at 50% 50%, transparent 4px, var(--ink) 4.5px, var(--ink) 5px, transparent 5.5px);
background-size: 32px 32px;
}
.cross-mat {
opacity: .06;
background-image:
linear-gradient(var(--ink), var(--ink)),
linear-gradient(90deg, var(--ink), var(--ink));
background-size: 1px 14px, 14px 1px;
background-position: center, center;
background-repeat: repeat;
}
/* ---------- 6. Typography roles (Swiss extreme contrast) ----------
Hard rule: "the larger, the lighter"
≥ 200px → weight 200 (ExtraLight)
Smaller text gets heavier weight (≥ 500). */
.h-hero {
font-family: var(--sans);
font-weight: 200;
font-size: 240px;
line-height: 1.02;
letter-spacing: -.02em;
margin: 0;
color: var(--ink);
}
.h-statement {
font-family: var(--sans);
font-weight: 200;
font-size: 180px;
line-height: 1.05;
letter-spacing: -.015em;
margin: 0;
color: var(--ink);
}
.h-xl {
font-family: var(--sans);
font-weight: 300;
font-size: 120px;
line-height: 1.08;
letter-spacing: -.01em;
margin: 0;
color: var(--ink);
}
.h-md {
font-family: var(--sans);
font-weight: 400;
font-size: 56px;
line-height: 1.18;
margin: 0;
color: var(--ink);
}
.lead {
font-family: var(--sans-zh);
font-weight: 400;
font-size: 30px;
line-height: 1.55;
color: var(--ink);
margin: 0;
}
.body {
font-family: var(--sans-zh);
font-weight: 400;
font-size: 26px;
line-height: 1.6;
color: var(--ink);
margin: 0;
}
.t-cat {
font-family: var(--sans);
font-weight: 600;
font-size: 22px;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--accent);
margin: 0;
}
.t-meta {
font-family: var(--mono);
font-weight: 500;
font-size: 20px;
letter-spacing: .14em;
text-transform: uppercase;
color: var(--grey-3);
margin: 0;
}
.num-mega {
font-family: var(--sans);
font-weight: 200;
font-size: 200px;
line-height: .92;
letter-spacing: -.03em;
color: var(--ink);
margin: 0;
}
.num-xl {
font-family: var(--sans);
font-weight: 200;
font-size: 144px;
line-height: .96;
letter-spacing: -.02em;
margin: 0;
}
.mono { font-family: var(--mono); }
/* XHS (3:4) boards: 1080×1440 has the tightest vertical budget,
so display sizes step down vs the .wide default. These caps
were validated by the smoke-ai-tools deck — 2-line h-xl @96px
leaves room for matrix + hero-stat-bottom inside 1440px. */
.poster.xhs .h-hero { font-size: 168px; }
.poster.xhs .h-statement { font-size: 124px; }
.poster.xhs .h-xl { font-size: 96px; }
.poster.xhs .num-mega { font-size: 168px; }
.poster.xhs .num-xl { font-size: 120px; }
/* Square boards tighten scales */
.poster.square .h-hero { font-size: 180px; }
.poster.square .h-statement { font-size: 140px; }
.poster.square .h-xl { font-size: 88px; }
.poster.square .num-mega { font-size: 168px; }
.poster.square .num-xl { font-size: 112px; }
/* Wide boards rely on horizontal real estate */
.poster.wide .h-hero { font-size: 200px; }
.poster.wide .h-statement { font-size: 156px; }
.poster.wide .h-xl { font-size: 104px; }
/* ---------- 7. Layout primitives ---------- */
.stack { display: flex; flex-direction: column; }
.row { display: flex; flex-direction: row; }
.gap-3 { gap: var(--sp-3); }
.gap-4 { gap: var(--sp-4); }
.gap-5 { gap: var(--sp-5); }
.gap-6 { gap: var(--sp-6); }
.gap-7 { gap: var(--sp-7); }
.gap-8 { gap: var(--sp-8); }
.gap-9 { gap: var(--sp-9); }
.gap-10 { gap: var(--sp-10); }
.grow { flex: 1; }
.grid-12 { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--sp-7); }
.grid-2-9 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-9); }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-7); }
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-6); }
.span-2 { grid-column: span 2; }
.span-3 { grid-column: span 3; }
.span-4 { grid-column: span 4; }
.span-6 { grid-column: span 6; }
.span-8 { grid-column: span 8; }
.span-9 { grid-column: span 9; }
.span-12 { grid-column: span 12; }
.center { text-align: center; }
.right { text-align: right; }
.uppercase{ text-transform: uppercase; }
.hr-hairline {
height: 1px;
background: var(--grey-2);
border: 0;
margin: 0;
}
.hr-accent {
height: 3px;
background: var(--accent);
border: 0;
width: 96px;
margin: 0;
}
/* ---------- 8. Card fills (mutually exclusive — pick ONE) ----------
Hard rule: never combine card-ink/card-accent/card-fill/card-outlined
on the same node. Multi-card grids must use the same class. */
.card-ink {
background: var(--ink);
color: var(--paper);
padding: var(--sp-8);
}
.card-ink .t-cat,
.card-ink .t-meta,
.card-ink .lead,
.card-ink .body,
.card-ink .h-md { color: var(--paper); }
.card-accent {
background: var(--accent);
color: var(--accent-on);
padding: var(--sp-8);
}
.card-accent .t-cat,
.card-accent .t-meta,
.card-accent .lead,
.card-accent .body,
.card-accent .h-md { color: var(--accent-on); }
.card-fill {
background: var(--grey-1);
color: var(--ink);
padding: var(--sp-8);
}
.card-outlined {
background: transparent;
color: var(--ink);
border: 1px solid var(--grey-2);
padding: var(--sp-8);
}
/* ---------- 9. Frame containers (image holders) ---------- */
.frame-img {
position: relative;
overflow: hidden;
background: var(--grey-1);
display: block;
margin: 0;
}
.frame-img img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center 35%;
display: block;
}
.frame-img.fit-contain img { object-fit: contain; background: var(--paper); }
.frame-img.r-3x4 { aspect-ratio: 3 / 4; }
.frame-img.r-1x1 { aspect-ratio: 1 / 1; }
.frame-img.r-4x3 { aspect-ratio: 4 / 3; }
.frame-img.r-16x9 { aspect-ratio: 16 / 9; }
.frame-img.r-16x10 { aspect-ratio: 16 / 10; }
.frame-img.r-21x9 { aspect-ratio: 21 / 9; }
.swiss-img-caption {
font-family: var(--mono);
font-weight: 500;
font-size: 18px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--grey-3);
margin-top: var(--sp-5);
}
/* ---------- 9b. Screenshot treatment (.frame-shot) ---------- */
/* Swiss is stricter than Editorial: corners default square, shadows default
none. Override only when the shot must literally float off the page. */
.frame-shot {
position: relative;
overflow: hidden;
display: block;
margin: 0;
background: var(--grey-1);
border-radius: 0;
}
.frame-shot.corners-sq { border-radius: 0; }
.frame-shot.corners-sm { border-radius: 4px; }
.frame-shot.corners-md { border-radius: 10px; }
/* Swiss shadows are flat — only the subtle outline scrim is welcome */
.frame-shot.shadow-soft { box-shadow: 0 12px 28px -16px rgba(0,0,0,.18); }
.frame-shot.shadow-ed { box-shadow: 0 20px 40px -20px rgba(0,0,0,.22), 0 0 0 1px var(--grey-2); }
/* background tones — pure / dot / grid only. No accent colour fills. */
.frame-shot.bg-paper { background: var(--paper); }
.frame-shot.bg-grey-1 { background: var(--grey-1); }
.frame-shot.bg-ink { background: var(--ink); }
.frame-shot.bg-grid {
background-color: var(--paper);
background-image:
linear-gradient(to right, var(--grey-2) 1px, transparent 1px),
linear-gradient(to bottom, var(--grey-2) 1px, transparent 1px);
background-size: 48px 48px;
}
.frame-shot.bg-dot {
background-color: var(--paper);
background-image: radial-gradient(var(--grey-2) 1px, transparent 1px);
background-size: 18px 18px;
}
/* asset backgrounds — ported from PPT skill, real texture for screenshot wells.
Use ONLY when current accent matches the asset's accent (don't mix oranges and IKB). */
.frame-shot.bg-asset-ikb-dot { background: #0c1a3a url("../assets/screenshot-backgrounds/style-b/ikb-dot-gradient.webp") center/cover no-repeat; }
.frame-shot.bg-asset-lemon-green-dot { background: #d6e08a url("../assets/screenshot-backgrounds/style-b/lemon-green-dot-shadow.webp") center/cover no-repeat; }
.frame-shot.bg-asset-lemon-grid { background: #e6e9b8 url("../assets/screenshot-backgrounds/style-b/lemon-grid.webp") center/cover no-repeat; }
.frame-shot.bg-asset-safety-orange { background: #ff5b1f url("../assets/screenshot-backgrounds/style-b/safety-orange-halftone.webp") center/cover no-repeat; }
.frame-shot.inset-none > img,
.frame-shot.inset-none > .shot-body { padding: 0; }
.frame-shot.inset-sub { padding: 20px; }
.frame-shot.inset-bal { padding: 48px; }
.frame-shot.r-3x4 { aspect-ratio: 3 / 4; }
.frame-shot.r-1x1 { aspect-ratio: 1 / 1; }
.frame-shot.r-4x3 { aspect-ratio: 4 / 3; }
.frame-shot.r-16x9 { aspect-ratio: 16 / 9; }
.frame-shot.r-16x10 { aspect-ratio: 16 / 10; }
.frame-shot.r-21x9 { aspect-ratio: 21 / 9; }
.frame-shot.r-3x2 { aspect-ratio: 3 / 2; }
.frame-shot > img {
width: 100%;
height: 100%;
object-fit: contain;
object-position: center;
display: block;
border-radius: inherit;
}
.frame-shot.fit-cover > img { object-fit: cover; }
/* device chrome — CSS-only, no SVG */
.device-browser {
position: relative;
background: var(--paper);
border: 1px solid var(--grey-2);
border-radius: 6px;
overflow: hidden;
}
.device-browser::before {
content: '';
display: block;
height: 32px;
background: var(--grey-1);
border-bottom: 1px solid var(--grey-2);
background-image:
radial-gradient(circle at 16px 16px, var(--grey-3) 4px, transparent 4.5px),
radial-gradient(circle at 34px 16px, var(--grey-3) 4px, transparent 4.5px),
radial-gradient(circle at 52px 16px, var(--grey-3) 4px, transparent 4.5px);
}
.device-browser > .frame-shot,
.device-browser > img {
border-radius: 0;
display: block;
width: 100%;
}
.device-phone {
position: relative;
background: var(--ink);
border-radius: 28px;
padding: 12px;
box-shadow: 0 16px 36px -18px rgba(0,0,0,.32);
}
.device-phone > .frame-shot,
.device-phone > img {
border-radius: 18px;
display: block;
width: 100%;
}
/* ---------- 9c. Map block (.map-block) ---------- */
/* Static map for Swiss decks. Pins are pure HTML, no SVG text.
Use accent sparingly: at most one accent pin per board. */
.map-block {
position: relative;
background: var(--grey-1);
overflow: hidden;
aspect-ratio: 4 / 3;
width: 100%;
}
.map-block.r-16x10 { aspect-ratio: 16 / 10; }
.map-block.r-16x9 { aspect-ratio: 16 / 9; }
.map-block.r-1x1 { aspect-ratio: 1 / 1; }
.map-block.r-3x4 { aspect-ratio: 3 / 4; }
.map-block > img,
.map-block > svg {
position: absolute; inset: 0;
width: 100%; height: 100%;
display: block;
}
.map-block > img { object-fit: cover; }
/* desaturated tile filter to keep Swiss strict tone */
.map-block.tone-paper > img { filter: saturate(0) contrast(1.05) brightness(1.02); }
.map-block.tone-ink > img { filter: saturate(0) brightness(.58); }
/* schematic SVG defaults */
.map-block > svg .map-coast { fill: none; stroke: var(--grey-2); stroke-width: .4; }
.map-block > svg .map-road { fill: none; stroke: var(--grey-3); stroke-width: .28; stroke-dasharray: 1.2 .8; opacity: .7; }
.map-block > svg .map-water { fill: var(--grey-1); }
.map-block > svg .map-grid { stroke: var(--grey-2); stroke-width: .15; opacity: .5; }
.map-pin {
position: absolute;
width: 0; height: 0;
transform: translate(-50%, -50%);
}
.map-pin .dot {
position: absolute;
left: -6px; top: -6px;
width: 12px; height: 12px;
border-radius: 50%;
background: var(--ink);
border: 2px solid var(--paper);
box-shadow: 0 1px 3px rgba(0,0,0,.22);
}
.map-pin.accent .dot { background: var(--accent); }
.map-pin .line {
position: absolute;
left: 7px; top: 0;
width: 28px; height: 1px;
background: var(--ink);
opacity: .55;
}
.map-pin.left .line { left: auto; right: 7px; }
.map-pin .card {
position: absolute;
left: 38px; top: -20px;
min-width: 80px;
padding: 8px 10px;
background: rgba(250,250,248,.94);
box-shadow: 0 0 0 1px var(--grey-2);
border-radius: 2px;
white-space: nowrap;
}
.map-pin.left .card { left: auto; right: 38px; }
.map-pin .card .name {
font-family: var(--sans-zh), var(--sans);
font-weight: 500;
font-size: 15px;
line-height: 1.1;
color: var(--ink);
}
.map-pin .card .meta {
display: block;
margin-top: 4px;
font-family: var(--mono);
font-size: 10px;
letter-spacing: .14em;
text-transform: uppercase;
color: var(--grey-3);
}
.map-pin.accent .card .name { color: var(--accent); }
.map-legend {
position: absolute;
left: 14px; bottom: 14px;
z-index: 3;
background: rgba(250,250,248,.94);
padding: 8px 12px;
box-shadow: 0 0 0 1px var(--grey-2);
font-family: var(--mono);
font-size: 11px;
letter-spacing: .14em;
text-transform: uppercase;
color: var(--grey-3);
}
.map-legend.tr { left: auto; right: 14px; top: 14px; bottom: auto; }
/* ---------- 10. Swiss-specific components ---------- */
/* S08 Image Hero (port of S22) */
.image-hero {
display: grid;
grid-template-rows: auto 1fr;
gap: var(--sp-7);
height: 100%;
}
.image-hero .hero-img-wrap {
position: relative;
overflow: hidden;
background: var(--grey-1);
}
.image-hero .hero-img-wrap img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center 35%;
display: block;
}
.image-hero .hero-overlay-block {
position: absolute;
left: 0;
top: 0;
background: var(--paper);
padding: var(--sp-7) var(--sp-8);
max-width: 60%;
}
.image-hero .hero-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--sp-8);
align-items: end;
border-top: 1px solid var(--grey-2);
padding-top: var(--sp-7);
}
.image-hero .hero-stats .stat-block .num {
font-family: var(--sans);
font-weight: 200;
font-size: 96px;
line-height: 1;
letter-spacing: -.02em;
color: var(--ink);
margin: 0 0 var(--sp-4);
}
.image-hero .hero-stats .stat-block .lbl {
font-family: var(--mono);
font-size: 18px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--grey-3);
margin: 0;
}
/* S09 KPI Tower (port of S06) */
.kpi-tower-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--sp-7);
align-items: end;
height: 100%;
}
.kpi-tower-row .tower-col {
display: flex;
flex-direction: column;
gap: var(--sp-5);
align-items: flex-start;
}
.kpi-tower-row .tower-col .num {
font-family: var(--sans);
font-weight: 200;
font-size: 112px;
line-height: 1;
letter-spacing: -.02em;
margin: 0;
}
.kpi-tower-row .tower-col .lbl {
font-family: var(--mono);
font-size: 20px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--grey-3);
margin: 0;
}
.kpi-tower-row .tower-col .bar-tower {
width: 100%;
height: var(--h, 240px);
background: var(--accent);
transform-origin: bottom;
}
.kpi-tower-row .tower-col.muted .bar-tower {
background: var(--grey-2);
}
.poster.xhs .kpi-tower-row { grid-template-columns: 1fr 1fr; gap: var(--sp-9); }
.poster.square .kpi-tower-row { grid-template-columns: 1fr 1fr; gap: var(--sp-9); }
/* S10 H-Bar Chart (port of S07) */
.h-bar-chart {
display: grid;
gap: var(--sp-6);
}
.h-bar-chart .bar-row {
display: grid;
grid-template-columns: 220px 1fr 120px;
gap: var(--sp-6);
align-items: center;
}
.h-bar-chart .bar-row .row-lbl {
font-family: var(--sans-zh);
font-weight: 500;
font-size: 26px;
color: var(--ink);
}
.h-bar-chart .bar-row .row-track {
position: relative;
height: 28px;
background: var(--grey-1);
}
.h-bar-chart .bar-row .row-fill {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: var(--w, 50%);
background: var(--accent);
}
.h-bar-chart .bar-row .row-val {
font-family: var(--mono);
font-weight: 500;
font-size: 26px;
letter-spacing: .04em;
text-align: right;
color: var(--ink);
}
/* On portrait the labels move above the track */
.poster.xhs .h-bar-chart .bar-row {
grid-template-columns: 1fr;
gap: var(--sp-4);
}
.poster.xhs .h-bar-chart .bar-row .row-val {
text-align: left;
}
/* S11 Stacked Ledger (port of S20) */
.stacked-ledger {
display: flex;
flex-direction: column;
}
.stacked-ledger .ledger-row {
display: grid;
grid-template-columns: 280px 1fr auto;
gap: var(--sp-7);
align-items: baseline;
padding: var(--sp-7) 0;
border-bottom: 1px solid var(--grey-2);
}
.stacked-ledger .ledger-row:first-child { padding-top: 0; }
.stacked-ledger .ledger-row:last-child { border-bottom: 0; }
.stacked-ledger .ledger-num {
font-family: var(--sans);
font-weight: 200;
font-size: 112px;
line-height: 1;
letter-spacing: -.02em;
margin: 0;
}
.stacked-ledger .ledger-lbl {
font-family: var(--sans-zh);
font-weight: 500;
font-size: 30px;
line-height: 1.3;
color: var(--ink);
}
.stacked-ledger .ledger-lbl .sub {
display: block;
font-weight: 400;
font-size: 22px;
color: var(--grey-3);
margin-top: var(--sp-3);
}
.stacked-ledger .ledger-icn {
width: 56px;
height: 56px;
color: var(--accent);
}
.poster.square .stacked-ledger .ledger-num { font-size: 88px; }
.poster.square .stacked-ledger .ledger-row { grid-template-columns: 220px 1fr auto; }
/* S12 Matrix Fill (port of S15) */
.matrix-fill {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 1fr;
gap: var(--sp-5);
}
.matrix-fill .matrix-cell {
background: var(--grey-1);
padding: var(--sp-6);
display: flex;
flex-direction: column;
gap: var(--sp-4);
min-height: 160px;
}
.matrix-fill .matrix-cell .cell-nb {
font-family: var(--mono);
font-size: 18px;
letter-spacing: .12em;
color: var(--grey-3);
}
.matrix-fill .matrix-cell .cell-title {
font-family: var(--sans-zh);
font-weight: 500;
font-size: 26px;
line-height: 1.3;
color: var(--ink);
}
.matrix-fill .matrix-cell.is-accent {
background: var(--accent);
color: var(--accent-on);
}
.matrix-fill .matrix-cell.is-accent .cell-nb,
.matrix-fill .matrix-cell.is-accent .cell-title { color: var(--accent-on); }
.hero-stat-bottom {
margin-top: var(--sp-9);
padding-top: var(--sp-7);
border-top: 1px solid var(--grey-2);
display: grid;
grid-template-columns: 1fr auto;
align-items: end;
gap: var(--sp-7);
}
/* xhs uses 2 columns; up to 8 cells = 4 rows. Switch to min-content
so rows don't stretch and crowd out hero-stat-bottom (validated
in smoke-ai-tools/xhs-04). When pairing matrix-fill with
hero-stat-bottom inside .poster.xhs, prefer 6 cells (3 rows). */
.poster.xhs .matrix-fill {
grid-template-columns: repeat(2, 1fr);
grid-auto-rows: min-content;
gap: var(--sp-4);
}
.poster.xhs .matrix-fill .matrix-cell { min-height: 0; padding: var(--sp-5); }
.poster.xhs .matrix-fill .matrix-cell .cell-title { font-size: 24px; }
.poster.xhs .hero-stat-bottom {
margin-top: var(--sp-6);
padding-top: var(--sp-5);
align-items: center;
}
.poster.xhs .hero-stat-bottom .num-mega { font-size: 128px; line-height: 1; }
.poster.square .matrix-fill{ grid-template-columns: repeat(3, 1fr); grid-auto-rows: auto; }
.poster.square .matrix-fill .matrix-cell { min-height: 0; }
/* ---------- 11. Issue label + corner metadata ---------- */
.chrome-min {
display: flex;
justify-content: space-between;
align-items: center;
font-family: var(--mono);
font-size: 18px;
letter-spacing: .14em;
text-transform: uppercase;
color: var(--grey-3);
padding-bottom: var(--sp-6);
border-bottom: 1px solid var(--grey-2);
margin-bottom: var(--sp-9);
}
/* ---------- 12. Pair preview frame ---------- */
.pair-preview {
width: 2400px;
min-height: 1180px;
background: var(--grey-1);
padding: var(--sp-9);
display: grid;
grid-template-columns: 2100px 1080px;
grid-template-rows: 900px 1080px;
gap: var(--sp-9);
align-content: center;
justify-content: center;
}
.pair-preview .preview-wide { grid-column: 1; grid-row: 1 / span 2; align-self: center; }
.pair-preview .preview-square { grid-column: 2; grid-row: 1 / span 2; align-self: center; }
.pair-preview .preview-label {
font-family: var(--mono);
font-weight: 500;
font-size: 18px;
letter-spacing: .14em;
text-transform: uppercase;
color: var(--grey-3);
margin-bottom: var(--sp-5);
}
/* ---------- 13. Hard rules (visual guard rails) ----------
Swiss style refuses these — uncomment to find offenders.
.poster *[style*="border-radius"],
.poster *[style*="box-shadow"],
.poster *[style*="linear-gradient"] { outline: 4px solid red !important; }
*/
</style>
</head>
<body>
<!-- ACCENT_OVERRIDE
Change <html data-accent="…"> above to:
ikb | lemon-yellow | lemon-green | safety-orange
One deck — one accent. Never combine.
-->
<main class="sheet">
<!-- POSTERS_HERE -->
<!--
Replace the placeholder poster below with real content.
Each poster carries one Layout Recipe (S01-S12) from
references/layout-recipes.md.
Available board sizes:
<section class="poster xhs" id="xhs-01">…</section> 3:4
<section class="poster square" id="wechat-1x1">…</section> 1:1
<section class="poster wide" id="wechat-21x9">…</section> 21:9
For WeChat covers, also include a <section class="pair-preview">
that places the 21:9 + 1:1 side-by-side for review.
Hard rules every poster must honor:
- exactly one --accent in the entire deck
- no border-radius, no box-shadow, no gradients
- card-ink / card-accent / card-fill / card-outlined are
mutually exclusive — never combine on one node
- "the larger, the lighter": ≥ 200px must use weight 200
-->
<!-- ============================================================
Placeholder: S01 Accent Cover (3:4)
Replace with real cover content from layout-recipes.md.
============================================================ -->
<section class="poster xhs" id="xhs-01">
<div class="content stack gap-9">
<div class="chrome-min">
<span>Vol. 01 · Swiss</span>
<span>2026.05</span>
</div>
<div class="stack gap-7">
<p class="t-cat">封面 · Cover</p>
<h1 class="h-statement">替换<br>标题</h1>
</div>
<div class="grow"></div>
<hr class="hr-accent">
<p class="lead">用一句克制的解释,告诉读者这一组卡片在讲什么。</p>
<div class="row gap-6">
<p class="t-meta">Issue 01</p>
<p class="t-meta">/ [作者]</p>
</div>
</div>
</section>
</main>
<!-- Lucide icon initialization (used by stacked ledger / matrix accents) -->
<script>
if (window.lucide && typeof window.lucide.createIcons === 'function') {
window.lucide.createIcons();
}
</script>
</body>
</html>
Handoff — guizang-social-card-skill
最后更新:2026-05-28 · 版本:v0.14(截图美化素材迁移:9 张 WebP 背景从 PPT skill port 过来 + 两个种子加 9 条 .bg-asset-*)
这份文档只记录事实:当前 skill 由哪些文件组成、每个文件管什么、本轮更新改了什么、怎么验证它还能跑。想读"为什么这样做"或"未来要怎么走",去看 PRODUCT.md。
v0.13 路径约定:本文件 v0.12 及更早版本历史段落里的demo-*/social-card-*/smoke-*/test-*目录已统一搬入local-tests/。读历史时请在脑中前缀local-tests/。
---
1. 目录结构(事实版)
guizang-social-card-skill/
├── SKILL.md # 7 步工作流入口
├── HANDOFF.md # 本文件
├── PRODUCT.md # 产品思考
├── assets/
│ ├── template-editorial-card.html # 杂志风种子
│ ├── template-swiss-card.html # 瑞士风种子
│ ├── magazine-bg-webgl.js # WebGL 墨流背景
│ └── screenshot-backgrounds/ # 9 张 WebP 截图舞台底(v0.14 从 PPT skill port)
│ ├── style-a/ # 5 张 Editorial(dune / forest-ink / indigo-porcelain / kraft-paper / monocle-classic)
│ └── style-b/ # 4 张 Swiss(ikb-dot / lemon-green-dot / lemon-grid / safety-orange)
├── references/ # 子规范,按需读取
│ ├── platform-specs.md 平台 × 分辨率 × 命名
│ ├── style-system.md 两种风格的硬规则与反模式
│ ├── theme-presets.md 6 套杂志 palette(含 Midnight Ink)+ 4 套 Swiss accent
│ ├── layout-recipes.md 版式骨架(M01-M16 + S01-S12 + WeChat)
│ ├── components.md 字体 / 卡片 / 间距 / 图标 / 标题字数表
│ ├── background-systems.md 墨流 / 网格 / 纸纹层
│ ├── portrait-fill.md 3:4 板的留白对策
│ ├── content-planning.md 钩子 / 拆页 / 文案压缩
│ ├── production-workflow.md 渲染管线与命令
│ ├── image-overlay.md 文字压图:选图 / 局部 tint / 主体映射
│ ├── screenshot-treatment.md 截图处理
│ ├── map-component.md Mapbox / OSM / schematic 地图
│ ├── title-shortener.md 公众号 1:1 短标题
│ ├── category-cookbook.md 小红书 11 个品类的版式路由
│ └── qa-checklist.md 交付前 checklist
├── agents/
│ └── openai.yaml # 子代理配置(备用)
├── validate-social-deck.mjs # 6 条规则的渲染后校验器
├── package.json / package-lock.json / node_modules # 仅 playwright
├── .gitignore # v0.13 新增
└── local-tests/ # 本地测试与素材(git 忽略,不随 skill 分发)
├── demo-* # 各次设计案例(kyoto / yading / wukong / kit-* / zzz-* / m16-camping / eyeliner / showcase / smoke-editorial-travel / map-component / screenshot-treatment / multi-platform)
├── social-card-* # 历史与 Codex 外部测试产物(含 codex-remote-* / wukong-breakout / kyoto-guide / moon-eyes-lip-mud)
├── smoke-ai-tools/ # v0.6 冒烟测试样例
├── test-dark-theme-elden-ring/ # v0.12 Midnight Ink 验证
├── Image/ # 用户上传的测试图
├── 全图封面参考/ # v0.10 杂志封面参考图
├── .codepilot-uploads/ # 历史上传缓存
└── *.cjs # 旧的 debug / probe / render 脚本v0.13 起:git 仓库。local-tests/ 整目录在 .gitignore 中,commit 只跟踪 skill 内容。
---
2. 数据规模与覆盖面
| 维度 | 数量 |
|---|---|
| 工作流步骤 | 7 步(Intake → Extract → Style → Plan → Seed → Build → Image → Deliver) |
| 风格模式 | 2 个(Editorial Magazine × E-ink / Swiss International) |
| 主题色 | Editorial 6 套(5 浅 + 1 暗 Midnight Ink)+ Swiss 4 套 = 10 套 |
| 版式骨架 | Editorial 16 个(M01-M16)+ Swiss 12 个(S01-S12)= 28 个 |
| 平台 × 比例 | 小红书 3:4 / 1:1,微信 21:9 + 1:1 配对,方封面 1:1 |
| 小红书品类覆盖 | 11 个品类中 7 个完整支持,4 个需用户提供素材,4 个明确不接 |
| 种子模板 | 2 份(Editorial / Swiss),单文件即含全部 class 与字体 |
| 已交付案例 | 7 个 demo(2 个 image-cover + 5 个 kit) |
---
3. 版本历史
仅记录"什么时候新增/变更了哪些可见资产",不记录文风调整。
v0.14 · 2026-05-28(本轮)
主题:截图美化素材从 PPT skill 迁移。v0.11 当时只 port 了规则文档(references/screenshot-treatment.md 154 行)和工具类(.frame-shot.* + .device-browser / .device-phone),但 PPT skill 的真实材质 WebP 背景整目录没拷过来,导致 .bg-paper / bg-grid / bg-dot 这些舞台底全是纯 CSS 实色,截图密度高的卡片视觉偏薄。本轮把素材补齐。
改动事实:
- 新增
assets/screenshot-backgrounds/,整目录 1.8 MB / 9 张 WebP: style-a/5 张 Editorial:dune.webp/forest-ink.webp/indigo-porcelain.webp/kraft-paper.webp/monocle-classic.webpstyle-b/4 张 Swiss:ikb-dot-gradient.webp/lemon-green-dot-shadow.webp/lemon-grid.webp/safety-orange-halftone.webpassets/template-editorial-card.html加 5 条.frame-shot.bg-asset-*规则,指向style-a/五张图(带 fallback 实色防止素材未加载时透明)assets/template-swiss-card.html加 4 条.frame-shot.bg-asset-*规则,指向style-b/四张图references/screenshot-treatment.md加 "Solidbg-*vs assetbg-asset-*" 小节:两张对照表 + 4 条 rules of thumb(不混 accent / 不叠shadow-ed/ 一致使用 / 高比例板不用)+ 路径说明- 同文件 Style cheat-sheet 加两条 recipe:Editorial hero with real texture / Swiss hero with brand-aligned stage
HANDOFF.md目录结构段加screenshot-backgrounds/子目录视图
重要约定:
- 素材路径用相对 URL
../assets/screenshot-backgrounds/style-{a|b}/{name}.webp,假设 deck 的index.html在local-tests/<task>/下(比assets/低一层)。放别处需要在 deck 里本地 override 路径 - Swiss 资产强 accent 绑定:
bg-asset-ikb-dot只能在data-accent="ikb"的 deck 用,不允许混色 - 不要在 asset 背景上叠
shadow-ed——1px描边会和材质冲突,读起来像 SaaS 框 r-3x4/r-1x1高比例板仍优先用 solidbg-*,asset 材质在过裁切时会变怪- 这是素材迁移,不是规则迁移:PPT skill 原版
screenshot-framing.md的 7 参数语义系统未引入(本 skill 走 CSS 工具类路线,决策更轻)
v0.13 · 2026-05-27(上一轮)
主题:git 初始化 + 仓库瘦身。把所有本地测试 / demo / 上传图 / 旧脚本统一搬到 local-tests/,写 .gitignore 忽略整个测试目录与 node_modules/。skill 本身只保留:SKILL.md / HANDOFF.md / PRODUCT.md / assets/ / references/ / agents/ / validate-social-deck.mjs / package.json 与锁文件。
改动事实:
- 新建
local-tests/,搬入 28 个测试目录(demo- 16 个 / social-card- 6 个 / smoke- 1 个 / test- 1 个 / Image / 全图封面参考 / .codepilot-uploads)以及 3 个旧脚本(render.cjs/debug-bg.cjs/probe-webgl.cjs) - 新增
.gitignore:忽略local-tests//node_modules//.DS_Store/*.log/.vscode//.idea/ HANDOFF.md目录结构段同步更新,列入local-tests/子目录视图- skill 自身文件结构未变,模板与规则文档一律未改
重要约定:local-tests/ 不进 git,不分发,不放入 skill 包。demo-showcase/editorial.html 和 demo-showcase/swiss.html 仍然是字号/字距/版式还原的 source-of-truth——它们移到了 local-tests/demo-showcase/,路径变了但作用未变。所有后续 demo / 冒烟测试都应在 local-tests/ 下新建子目录。
v0.12 · 2026-05-27
主题:Editorial 美学回退 + 第 6 个杂志主题 Midnight Ink + 图片裁切策略 + 交付工作流调整。本轮起因是用户对 v0.11 冒烟测试的"质量回退"反馈——v0.10/v0.11 在把 showcase 模板化进 seed 的过程中,把 Editorial 的字重/字距/正文字族无意中改成了"信息图横幅"风(900 字重 / 负字距 / sans 正文),与 demo-showcase/editorial.html 的"克制磁带感"完全偏离。同时 Codex 外部测试 social-card-wukong-breakout 暴露了缺暗色主题、自造 wukong-night 不合规的问题。
新增
assets/template-editorial-card.html增加[data-theme="midnight-ink"]暗色 palette + 三段必随覆盖(.grain翻成 screen-mode 暖光斑 /.paper-wash改成金辉 + 阴影 vignette /.frame-img加深底色 + 1px 暖边)。Editorial palette 从 5 套变 6 套。references/theme-presets.md增 Midnight Ink 段,含色值 + 必随覆盖代码 + 使用场景(游戏 key art / 夜景 / 影调封面)。明确"暗色 Editorial 只有这一套,不要自造第二套"。test-dark-theme-elden-ring/— Midnight Ink 验证 deck(4 张 1080×1440,《艾尔登法环》新手指南)。图源 Steam 官方 8 张截图,记录在assets/。验证 P1 全幅 hero-bleed + 黄金 accent、P2 M14 pipeline 4 步 + 21:9 atmosphere、P3 M07 ledger 4 行 + 战斗 atmosphere、P4 quote-card + 4:3 静态时刻。
修改:Editorial 美学回退(核心)
assets/template-editorial-card.html全部 Editorial 排版默认值恢复 showcase 美学:.h-display124px weight 500 letter-spacing +.04em(v0.10/v0.11 误改为 900 / −.01em).h-xl88px weight 500 +.03em(曾经 700 / −.01em).h-md56px weight 500 +.02em.lead28px font-family: var(--serif-zh)(曾经被切到 sans-zh,是塌掉杂志感的主因).body24px serif-zh(同上).kicker21px letter-spacing +.22em.meta/.label18px +.20em.ledger-row .ledger-title42px weight 500,.ledger-note22px serif-zh.pipeline-v .step-title40px weight 500,.step-desc24px serif-zhreferences/components.mdEditorial 字号表完整改写为上述值,加一段 Restored 2026-05-27 警示,写明"v0.10/v0.11 错改值是什么,为何会塌掉,showcase 是 source-of-truth"。references/style-system.mdMode A 加 Typography stance — "the larger, the lighter" 段:Editorial display 必须 500、serif body、宽字距;反模式(700-900 + sans + 负字距)= "infographic banner" 塌方。
修改:图片裁切策略
assets/template-editorial-card.html.frame-img img默认object-position从center top改为center 50%。Editorial 之前的center top是默认值意外,导致竖图主体被切掉;showcase 实际上是逐图 inline 覆盖。references/components.mdImage Containers 段加 主体感知裁切表(主体位置 → object-position 取值)+ 硬规则"每张图都要 inline 写 object-position,默认值是 fallback 不是推荐"。SKILL.mdStep 6 Image and Screenshot Handling Crop discipline 改写为强制每张图 inline 写object-position,带具体取值示例(center 25%天空多 /center 50%居中 /center 62%半身 /center 70%前景物)。
修改:交付工作流("show user first, ask before validating")
SKILL.mdStep 7 Deliver 重写:- 默认 不再 渲完自动跑 validator。
- 渲完先把图给用户看,问一句"先你自己看,还是我先自动核查一遍?"。
- 用户说"自己看"→ 停下来等反馈;说"你查吧"→ 才跑
validate-social-deck.mjs。 - 起因:用户反馈每次渲完自动跑 validator 让交付窗口变得很长,用户大多数时候眼睛比脚本更快。
- 代价:自动校验的"安全网"被撤掉一格——但脚本仍然可用、用户随时可调。
修改:已有 demo 同步
demo-kyoto-trip/index.html— 字号回退到 showcase 值,P4 ledger 去掉 03(岚山)解决 3:4 溢出,title 改"四条建议"。demo-smoke-editorial-travel/index.html— 字号回退到 showcase 值。P1 hero 从r-16x10改r-3x2+object-position:center 62%(hero-grassland 主体在画面中下)。P5 camp-tarp 加center 55%。两份 demo 都 4-5 张 1080×1440 重渲过,无溢出。
Codex `social-card-wukong-breakout` 诊断
- 状态:未由本 skill 修复。Codex 在 v0.11 规则下做的测试。
- 根因:Codex 实际遵守了规则——错的是规则本身。
components.md当时还在写 900/700/sans-body 默认值,Codex 照搬出来必然塌;Codex 自造的wukong-night是"规则没暗色"的合理外推。 - 修复路径:v0.12 已落地 Midnight Ink 官方暗色 + 美学回退;后续 Codex 任务直接
data-theme="midnight-ink"即可,无需自造。
未做 1. 截图美化资产 port(仍在短期清单) 2. 地图组件(同上) 3. 1:1 短标题生成器(同上) 4. 多平台一次出包(同上) 5. social-card-wukong-breakout 未重新生成——v0.12 是规则修复,不回灌历史 Codex 测试
v0.11 · 2026-05-27
主题:短期规划 Phase A 收口 — validator + Editorial 冒烟测试。短期规划 6 件事完成第 1、2 件,把 v0.10 留下的 Editorial 长内容欠债(M11/M14 在 3:4 上从未单独验证)也清掉。
新增
validate-social-deck.mjs— 单文件 Node 脚本,接受 task-dir 或 index.html,对每个<section class="poster …">跑 6 条规则:R1 overflow / R2 footer 碰撞 / R3 Swiss 粗体 display / R4 字号下限 / R5 4 横带密度(pixel-row union 覆盖率)/ R6.h-xl行/字 cap。--style=swiss|editorial可显式指定,默认按data-theme/data-accent或 serif 字族 fallback 自动判断。退出码 1 仅在 FAIL 时;WARN 是 advisory。package.jsonscripts.validate— 把node validate-social-deck.mjs包到npm run validate <task-dir>。demo-smoke-editorial-travel/— Editorial 冒烟测试 5 张 1080×1440:M01 cover + M11 Marginalia Essay(3 段正文 + 7 行 marginalia)+ M07 Field Ledger(5 行账本)+ M14 Vertical Pipeline(5 步)+ M16 Image-Led Cover 收束。全部 PASS validator。references/components.md§ Editorial.h-xlHard Caps — 实测表:xhs 92px / square 78px / wide 96px;附 M11/M14/M07 各自的 ledger / paragraph / step 数硬上限。
修改
SKILL.mdStep 5 Build And Render 加:每次 render 后必须跑 validator,FAIL 必修。Step 7 Deliver 加:交付前再跑一次;WARN 在交付消息里报告让用户决定。validate-social-deck.mjs修两轮 false positive:- R2 footer collision:原版把
.content容器 + 全幅.mag-bg误判碰底;改成只看 leaf text/media + 跳过 ≥95% 全幅 absolute 节点。 - R4 min font:原版按 class 选择器命中所有
.lead,但 Swiss.role-card .lead是子组件文本。改成只查 leaf text 节点的角色。 references/components.md§ Image Containers 加:figcaption 类名硬规则——Editorial 用.img-cap、Swiss 用.swiss-img-caption、没有共享 `.cap`。冒烟测试中我把它写成.cap触发 R4,已固化为警示。
回归测试(在 5 个 demo 上跑过 validator)
demo-smoke-editorial-travel:5 pass / 0 fail / 0 warndemo-zzz-miyabi:4 pass / 1 R5 warn(p4 H-Bar 底部 trailing whitespace,可接受)demo-eyeliner:3 pass / 1 R5 warn(xhs-03 底带 13%,可接受)smoke-ai-tools:发现一处历史 R1 overflow(xhs-03 +101px)—— 之前漏看,已暴露demo-showcase/swiss.html+editorial.html:共暴露 39 + 46 条 R4/R5/R6 warn,主要是 lead/body/caption 在数据密集页里被压到 18-22px、几条 h-statement 行数超 cap。下一轮可逐张回修。
未做(同短期规划清单) 1. 截图美化资产 port(下一件) 2. 地图组件 3. 1:1 短标题生成器 4. 多平台一次出包
v0.10 · 2026-05-26
主题:内容密度硬规则 + 风格-内容类型解绑 + 全版式 showcase。两件事并轨:(1) 解决 3:4 卡内容欠填——把"杂志留白逻辑"和"社交卡逐张刷"这两件事在文档层面拆清;(2) 把"Editorial 只接生活类 / Swiss 只接产品类"的隐含绑定明确解除,让用户用同一篇内容自由选风格。
新增
demo-eyeliner/— 4 张 3:4 美妆类 Editorial 案例(kraft-paper),P1 M16 全幅产品图,P2 M03 翻车叙事,P3 M01 双头机制,P4 M07 收束。验证密度规则在"图+文+ledger"混合卡上的落地。demo-showcase-editorial/— 16 张 1080×1440 案例,逐张覆盖 M01-M16,循环用 5 套杂志 palette。demo-showcase-swiss/— 12 张 1080×1440 案例,逐张覆盖 S01-S12,循环用 4 套 Swiss accent。两套 showcase 演示「内容类型与风格脱钩」——美食内容也可走 Swiss、AI 工具也可走 Editorial。
修改
SKILL.mdStep 3 重写:"Editorial 用于 / Swiss 用于" 的硬绑定改成"两种风格是 visual stance,不是 content category;任何内容都可走任一风格;选风格按 editorial intent(feature story 还是 release note),不按 topic lookup。"SKILL.mdNon-Negotiables 增加 3:4 内容密度硬规则:内容 ≥75% 画布高度;>15% 空白带需"留白理由";禁用<div style="flex: 1"></div>上下夹击;杂志留白逻辑不适用于社交卡。references/style-system.md顶部增 "Style ↔ content type are decoupled" 段;Mode A / Mode B 的"Use for"硬列表改成"Good fits"软建议(含明确反例:workplace essay 也可走 Editorial、travel ledger 也可走 Swiss)。references/layout-recipes.md:- Editorial 和 Swiss recipe 总览段都加了"这些结构适用于任何 topic"的前言。
- 新增 Content density rule (hard) 段,写在 Editorial recipe 列表前。
- M03 / M04 / M07 / M13 各加 Minimum density 段:
- M03:title + 3 段 或 title + 2 段 + 编号 footer 列表。title 单独是 M04 不是 M03。
- M04:唯一允许 ≤60% 内容的 recipe,但必须有 3 个 anchor(source row / kicker / hairline)。
- M07:title + ≥4 ledger items with sub-lines + closing block。"3 短 ledger 行"是已知失败模式。
- M13:必备 anchor(top kicker + bottom prompt + bottom metadata + 可见 WebGL bg)。
references/qa-checklist.md:新增 4 横带密度检查(Filled / Justified empty / Under-filled 分类,>15% 画布的 under-filled 带判失败;通过门槛:Filled+Justified ≥100%、Filled ≥75%、相邻两带不能都 justified)。PRODUCT.md决策 1 重写:风格不再绑定内容品类。删除"Editorial × 人文/Swiss × 工程"的硬对应;明确"两种风格是视觉立场不是内容门"。
修复路径(落到代码)
- 杂志留白逻辑→社交卡的对策:用
flex-grow: 1+justify-content: space-between让"内容块本身撑开",禁用空<div style="flex:1"></div>作为占位。demo-eyelinerP2/P4 两次重渲染后通过 4 横带检查。 - M07 spec 从 "2-3 rules" 改成 "4-6 ledger items with sub-lines",每行 100-140px 垂直。
未变动
- Recipe 计数:仍是 M01-M16 + S01-S12 = 28 个。
- Theme palette 计数:5 + 4 = 9 套。
- 工作流步数仍 7 步;种子模板未动。
- 小红书品类能力圈未变;只是不再把"风格-品类"硬绑定。
对比验证
- 旧 P2/P4(v0.9 demo-eyeliner 第一版):内容散在画布中段,底部 25%-35% 是空白带,缩略图刷过去像 PPT 漏排。
- 新 P2/P4(v0.10):内容撑满到 ~92% 画布高度,引语+footer 紧贴底缘;FALLOUT 四条 a-d 均匀分布到引语之上。
- showcase 两套:28 个 recipe 每个出 1 张 1080×1440,全部通过 4 横带检查、覆盖全 9 调色、内容类型故意打散(户外 / AI / 食谱 / 数据 / 节气 / 影评等都既出现在 Editorial 也出现在 Swiss)。
v0.9 · 2026-05-26
主题:M16 编辑杂志化重写。v0.8 的 M16 借了"游戏 key art"那套(全幅黑色 vertical falloff + 132px 黑体感字 + 纯白文字),出来像旅游海报、不像 Kinfolk / Cereal。本轮按 8 张参考的共同语言推倒重来:选图是主要杠杆,遮罩是兜底;字小、字细、字色偏纸。
修改
references/image-overlay.mdRule 1 重写:"选图先,遮罩后,禁全幅 falloff"四步法。Step 1 选图门(quiet-zone 测试 + light 测试),Step 2 默认不加 mask 直接验,Step 3 兜底用 localized + image-toned 软渐变(peak α ≤ 0.30),Step 4 缩略图测试。Banned 段把"全幅 vertical falloff"和"纯黑 mask 色"明确点名禁掉。references/image-overlay.mdRule 2 增 Composition discipline 段:4 条编辑杂志构图硬规(非对称、≥60% 留白、标题只占一个 quiet zone、绝不跨越主体轮廓)。references/image-overlay.mdChecklist 重写:把"≥60% → 强制 mask"翻成"先看选图通不通过,mask 仅在 Step 4 失败时"。references/layout-recipes.mdM16 重写:- 顶部加 Photo qualification gate——quiet-zone test + light test 都过才能走 M16,否则退回 M01。
- Modes A/B/C/D 表里"Mask"列改成"Tint (only if Step 4 fails)",明确"先无 mask、不行再局部图调色软渐变"。
- 字号字色字体彻底改:标题 88-108px(v0.8 是 108-132px),Noto Serif SC 400-500(v0.8 没限制字重,default 700+),色
#f5f1e8纸米(v0.8 是 var(--paper) 容易跑成 #fff),letter-spacing 0.10-0.15em。 - 加 Forbidden in M16 type 段:禁 inline 700+ 字重、禁纯白
#fff、禁标题 > 120px、禁拉丁 sans 作主标题、禁 D 模式标题居中。 - HTML skeleton 两段重写:默认不加 mask、用
Noto Serif SC weight 500 + #f5f1e8,全 inline 字号(不依赖.h-display,因为种子模板.h-display强制color: var(--ink)在暗背景上失效)。 demo-m16-camping/index.htmlP1 封面重做:Mode A(顶 kicker + 底标题),无全幅 mask,仅底部 22%x84% 苔绿色 (rgba(20,32,22,...)) 软 radial 在标题区,标题 92px Noto Serif SC weight 500 / tracking 0.12em / 色#f5f1e8。HTML 注释加 photo qualification 记录。
未变动
- Editorial recipe 计数:仍是 M01-M16。
- Swiss recipe / theme / 工作流 / 种子模板没动。
- demo-m16-camping P2 (ledger) / P3 (field notes) 没动——它们用的是 M05/M02 文字主导版式,没踩 M16 的坑。
对比验证
- 旧 P1(v0.8):黑色 78% 侧栏 scrim + 132px 字 + 整页压重。读起来像旅游打卡。
- 新 P1(v0.9):林荫透出、字小克制、底部苔绿软渐渍只够支撑标题对比。读起来像 Kinfolk camping 专题。
- 缩略图测试(360px wide):标题 / kicker / 底部 mono strip 全部可读,无糊。
v0.8 · 2026-05-26
主题:图先序列 + 满图封面 recipe。给"生活化、吃图片"的内容一个明确的 P1 图占满 + P2-3 数据的版式。
新增
references/layout-recipes.md新增 M16 Image-Led Cover (Full-Bleed Hero):填补 M01"图+文分块"到"图占满 + 文字压上"的空白。含 4 种文字位置(顶压底沉 / 侧栏立柱 / 角落徽章 / 下沉条带,按 subject map 选)、字号 spec 表、标题长度上限表、1:1 / 21:9 适配、两段完整 HTML 骨架(Mode A + Mode D)。Editorial 模式专用,Swiss 不用。Editorial recipe 数:15 → 16。references/content-planning.md新增 Image-Led Sequence 段:P1 M16 满图 → P2 文字数据卡(S11/M05/M10)→ P3 M02 副图 → … 硬规则"绝不连续两张 M16"。
修改
references/category-cookbook.md:- 旅行 / 家居 / 美食(食谱)路由首选改为 M16 → S11/数据卡 → M02 的图先序列。
- 情感不强推图先序列(M16 不适合 quiet documentary 的 "窗 / 椅 / 手"——继续走 M04/M09/M11 文字主导)。
SKILL.mdSeed 引用范围M01-M15→M01-M16。
未变动
- 工作流步数与种子模板。
- Swiss 12 个 recipe / 9 套 palette / image-overlay 规则。
- M16 强依赖既有 image-overlay.md(mask + subject map + crop),没有放宽任何安全检查。
v0.7 · 2026-05-26(上一轮)
主题:Editorial 图源扩展。没有新增 layout,没有改种子。
修改
SKILL.mdStep 6 web-sourced 图源从 3 条扩到 5 条:新增 Pexels(#2,原生支持中文搜索,补 Unsplash 在国内场景的缺口)与 Flickr CC(#3,license=2,3,4,5,6,9过滤,补"纪实感真实感"的缺口)。加了取图顺序:Pexels(中文/国内)→ Unsplash → Flickr CC(纪实)→ direct search。明确"全部免版权图库,不接 视觉中国 / Getty / 站酷海洛 等付费源"。Swiss 模式继续不走图库。references/category-cookbook.md:- 旅行:Pexels 优先用于国内目的地(中文关键词);Flickr CC 用于"真实旅程"的纪实质感。
- 美食:Pexels 用于中式菜品 / 国内餐厅;Unsplash 标注为"西式 stock 感",仅做兜底。
- 情感:Flickr CC 升为主推(窗 / 椅 / 手这类纪实质感最契合);Pexels 补国内语境;Unsplash 仍然可用但避开"moodcore" gradient-light stock。
references/qa-checklist.md:图源 checklist 改为枚举 Pexels / Unsplash / Flickr CC / Wallhaven / direct search,并提示 Flickr CC 在用户同意标注时要保留作者署名。
新增
SKILL.mdStep 1 Intake 加"三选一"入口:用户只给文字时问一次(A. 自己提供照片 推荐 / B. 我帮你找 / C. AI 生成),按选择走 Step 6,不二次劝导。SKILL.mdStep 6 "When the user has no images" 改为依赖 Step 1 gate,明确不再自动跳到 B/C。
未变动
- 工作流步数(仍为 7 步,三选一是 Step 1 内的子项)与种子模板。
- 9 套 palette 与 27 个 recipe。
- 不接付费图库(视觉中国 / Getty)是产品边界(见
PRODUCT.md决策 4)。
v0.6 · 2026-05-26(上一轮)
主题:冒烟测试 + 种子模板硬化。没有新增 layout,没有改工作流。
新增
smoke-ai-tools/— 完整冒烟测试样例。主题"程序员一周 AI 工具使用周记"。用 5 张 1080×1440 跑通 S01/S07/S10/S11/S12 五个 recipe。Klein Blue accent。HANDOFF.md·PRODUCT.md(本文件 + 产品文档)
修改
assets/template-swiss-card.html:为.poster.xhs补全 display 字号 override。.h-hero168px ·.h-statement124px ·.h-xl96px ·.num-mega168px ·.num-xl120px.matrix-fill改为grid-auto-rows: min-content、min-height: 0、gap: var(--sp-4).matrix-cell .cell-title在 xhs 下降到 24px.hero-stat-bottom在 xhs 下紧凑 margin / padding,num-mega128pxreferences/components.md:新增两张实测对照表。- "Swiss
.h-xlHard Caps per Board" — 每板最多几行、单行最多几字、超限会怎样 - "Matrix + hero-stat-bottom on
.poster.xhs" — cell 数对应的搭配建议
已知遗留
- Editorial 模板(
template-editorial-card.html)未做同样的冒烟测试。M14 纵向 Pipeline 5 步在 3:4 板上是否溢出,未验证。 - 冒烟测试只覆盖 5 个 Swiss recipe(S01/S07/S10/S11/S12)。其余 S02-S06/S08/S09 未跑过真实小例子。
v0.5 · 2026-05-26 早些时候
主题:image-overlay 规则上线 + 文字压图验证。
新增
references/image-overlay.md— 文字压图规则:蒙版强制 + 多模态主体映射 + 缩略图测试demo-image-02-wukong/— 用《黑神话:悟空》主视觉验证 image-overlay 规则
修改
SKILL.mdStep 6 加入文字压图小节references/style-system.md新增 Anti-Pattern D(封面照片裸跑无蒙版)
v0.4 · 之前
主题:模板种子 + 9 个新版式补全(对齐已成熟的 PPT skill)。
新增
assets/template-editorial-card.html·assets/template-swiss-card.html两份种子references/components.md字体/卡片/间距/Lucide token 表layout-recipes.md加 M12-M15(Editorial 缺失节奏型)+ S08-S12(Swiss 5 个 port from PPT)
修改
SKILL.md加 Step 4.5(拷贝种子模板)- Required References 加
components.md
v0.3 及更早
SKILL.md + 9 份 reference 的初始体系。小红书品类能力圈(category-cookbook.md)。Editorial 11 个版式 + Swiss 7 个版式。
---
4. 如何验证 skill 还能跑
最快的验证路径,不要发明新需求。
cd /Users/guohao/Documents/code/HyperFrames-test/guizang-social-card-skill/local-tests/smoke-ai-tools
node render.mjs
ls output/ # 应该看到 xhs-01.png ... xhs-05.png
sips -g pixelWidth -g pixelHeight output/xhs-01.png # 应为 1080×14405 张图都能渲染、尺寸都对 → Swiss 种子模板 + 5 个 recipe 工作正常。
视觉验收:用 Read 工具读 output/xhs-04.png,确认大数字 "8" 在右下角完整可见、未被截断。
更全的 demo:local-tests/demo-image-02-wukong/index.html 用 Playwright 渲染,验证 image-overlay 规则下文字与人物未冲突。
---
5. 工作流要点(给接手者的极简版)
按 SKILL.md 第 7 步走,不要跳步。最易踩坑的两步:
- Step 3 不要混风格:Editorial 用 serif + ledger/marginalia;Swiss 用 Inter + card-fill/matrix。两套 class 命名空间隔离,模板间不能跨用。
- Step 4.5 必须拷种子:从零写 HTML 是 v0.4 之前的工作流,已废弃。
文字压图的页(封面 / 大图位 / 全幅图)四步:先 Read 图、写 HTML 注释记录主体位置、选 object-position、做 360px 缩略图检查。默认先不加遮罩;只有缩略图不可读时才加局部、取自图片色调的 tint。
---
6. 已知风险与限制(事实层面)
- 校验脚本是按需兜底。
validate-social-deck.mjs已可检查溢出、页脚碰撞、字号下限、密度、标题行数与 figure 默认 margin 漂移;默认交付流先给用户看,用户要求 auto-check 时再跑。 - 没有跨平台一次出包工具。一篇文章要同时出小红书 3:4 + 微信 21:9+1:1 + 方封面 1:1,目前要手工开两个任务文件夹。
- 品类能力圈未硬编码。
category-cookbook.md写明了 4 个不接的品类,但 skill 不会主动拒绝;需要执行者在 Step 1 自己读到并主动告知用户。 - 图片来源签名机制是"用户决定"。
SOURCES.md总会写,但是否在图上加标注由用户回答。这不是技术限制是产品决策(见PRODUCT.md)。
---
7. 不要做的事
- 不要改 PPT skill(在
~/.claude/skills/guizang-ppt-skill/)。本 skill 借了它的视觉原则但独立维护。 - 不要把 demo 文件夹删掉来"清理"。它们是回归测试样本。
- 不要在两份种子模板里塞动效系统、键盘翻页、ESC 索引——那是 PPT 的需求,社交卡是静态 PNG。
- 不要把 components.md 的硬上限表"调宽"以接更长的标题。先压文案。
{
"name": "guizang-social-card-skill",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "guizang-social-card-skill",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"playwright": "^1.60.0"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"hasInstallScript": true,
"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==",
"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==",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
}
}
}
{
"name": "guizang-social-card-skill",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"validate": "node validate-social-deck.mjs"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"playwright": "^1.60.0"
}
}
Background Systems
The original Guizang PPT electronic-magazine mode uses WebGL fluid, contour, ink, and chromatic atmosphere. Static Rednote/WeChat images should preserve that feeling when the style mode is Editorial Magazine x E-ink.
Do not reduce this mode to a flat beige page with a faint grid. Do not add visible grid, dot-matrix, or drafting-paper patterns to the background.
Layer Model
Use 3-5 layers:
1. Paper base from theme-presets.md. 2. Procedural paper grain. 3. Ink wash, contour field, or WebGL fluid canvas. 4. Optional hairline structure as content dividers only, not as a page-wide background grid. 5. Content layer.
Recommended CSS order:
<section class="poster magazine hero">
<canvas class="mag-bg" data-bg="ink-flow"></canvas>
<div class="grain"></div>
<div class="ink-vignette"></div>
<div class="content">...</div>
</section>.poster { position: relative; overflow: hidden; background: var(--paper); }
.mag-bg { position:absolute; inset:0; width:100%; height:100%; z-index:0; opacity:.34; }
.poster.hero .mag-bg { opacity:.62; }
.grain {
position:absolute; inset:0; z-index:1; pointer-events:none;
opacity:.12; mix-blend-mode:multiply;
background:
linear-gradient(112deg, rgba(0,0,0,.035), transparent 26%, rgba(255,255,255,.16) 52%, transparent 82%),
radial-gradient(circle at 18% 22%, rgba(var(--ink-rgb),.05), transparent 38%),
radial-gradient(circle at 78% 84%, rgba(var(--accent-rgb),.045), transparent 42%);
}
.ink-vignette {
position:absolute; inset:0; z-index:2; pointer-events:none;
background:
radial-gradient(circle at 18% 12%, rgba(var(--ink-rgb),.10), transparent 34%),
radial-gradient(circle at 82% 80%, rgba(var(--accent-rgb),.10), transparent 38%);
}
.content { position:relative; z-index:3; }When To Show WebGL
Use stronger visible atmosphere for:
- Cover.
- Chapter/divider.
- Pull quote.
- Sparse thesis page.
- Closing page.
Use subtle atmosphere for:
- Screenshot pages.
- Dense ledgers.
- Checklists.
- Product evidence pages.
If screenshots are the main evidence, the background should support them rather than compete with them.
WebGL Helper
The skill includes assets/magazine-bg-webgl.js, a small helper for a deterministic ink-flow background:
<canvas class="mag-bg" data-seed="workbuddy"></canvas>
<script src="assets/magazine-bg-webgl.js"></script>
<script>
document.querySelectorAll(".mag-bg").forEach((canvas, i) => {
MagazineBg.mount(canvas, {
ink: [26, 46, 31],
paper: [245, 241, 232],
accent: [46, 107, 79],
strength: canvas.closest(".hero") ? 0.62 : 0.28,
frozenTime: 2.4 + i * 0.37
});
});
</script>For screenshot exports, prefer frozenTime so repeated renders look stable.
2D Fallback
If WebGL fails, create a 2D canvas with:
- Large radial ink wash.
- Several translucent contour arcs.
- Soft paper noise or uneven ink wash.
- Theme color at very low opacity.
The output should still feel like an editorial background, not a plain fill.
Do Not
- Do not use bright gradients.
- Do not use page-wide grid, dot-matrix, graph-paper, or drafting-paper backgrounds.
- Do not use decorative blobs or circles with no relationship to the layout.
- Do not place strong background marks behind body text.
- Do not let WebGL obscure screenshots or small captions.
- Do not animate the final image sequence unless the task is video.
Platform Specs
Use high-resolution exports. The user can downsample later.
Xiaohongshu / Rednote
Default size:
1080 x 1440- Ratio:
3:4 - Export: PNG for text-heavy images, JPEG only when photo-only and file size matters.
Safe area:
- Side margin: 72-96px.
- Top margin: 72-112px.
- Bottom margin: 80-120px.
- Keep all text and key objects inside the safe area.
Recommended set:
- 1 cover.
- 4-8 content pages.
- 1 summary or checklist page if the article benefits from it.
Naming:
output/xhs-01-cover.png
output/xhs-02-<topic>.png
output/xhs-03-<topic>.png
...Cover structure:
- Big title with a sharp hook.
- One strong visual: supplied image, screenshot, generated photo, or product object.
- Bottom line with 3-5 key points or a compact issue strip.
- Avoid long subtitles. Put nuance in later pages.
Content page structure:
- One idea per page.
- 12-30 Chinese characters for the title when possible.
- 2-4 short bullet fragments or one concise paragraph.
- If a page has a screenshot, reduce the text and give the screenshot enough area.
- If lower space is empty across pages, combine pages or add a functional element such as checklist, comparison row, quote, or image evidence.
WeChat Official Account Cover Pair
Generate a pair: one 21:9 main cover and one 1:1 square cover. Build both in the same HTML file, and include a combined preview section that places them together for visual checking.
21:9 Main Cover
Default size:
2100 x 900- Ratio:
21:9
Use:
- Main article cover.
- Full or near-full headline, subtitle, and visual evidence.
- Keep title readable near center-left or within a clear safe band.
- Avoid an empty middle. Increase title scale, image scale, or spacing if the center feels hollow.
1:1 Cover
Default size:
1080 x 1080- Ratio:
1:1
Default style:
- Short title only, simplified from the long title.
- Big type, center aligned.
- No image unless the user explicitly asks for one.
- No subtitle by default.
- Strong contrast and enough breathing room.
Short-title rule:
- Extract the core object and action from the long title.
- Keep it to roughly 4-10 Chinese characters, or 2 short lines when needed.
- Do not squeeze the full
21:9title into the square.
Example:
Long: 开源了一个 Skill,让 AI 接管你屏幕边那张便签纸
Square: AI 接管便签纸Naming:
output/wechat-21x9-cover.png
output/wechat-1x1-cover.png
output/wechat-cover-pair-preview.pngScreenshot And Photo Placement
For supplied screenshots/photos:
- Put originals in
assets/. - Use CSS
object-fit:coveronly when cropping is safe. - Use CSS
object-fit:containfor UI screenshots, dense text, code, tables, or app windows. - Set
object-positiondeliberately: top for long UI when top matters, center for objects, 35%-45% vertical for portraits. - Do not pin images to the canvas edge unless the design intentionally uses bleed.
Final Response Media
In Codex desktop, local media can be shown with absolute paths:
Do this for the most important images, especially the cover and any pages the user asked to inspect.
Related skills
FAQ
What outputs does guizang-social-card-skill produce?
Xiaohongshu 3:4 carousel sets plus paired WeChat 21:9 main and 1:1 square covers built in shared HTML for preview.
When should the skill push back on a request?
When the Rednote category needs daily OOTD body shots, dreamcore aesthetics, or pure photography showcase posts outside its capability circle.
Is Guizang Social Card Skill safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.