v0.14: 截图美化素材迁移 + 散落改动归集
主线:从 PPT skill port 9 张截图舞台底 WebP,补齐 v0.11 当时只迁规则未迁素材的缺口。 新增 - assets/screenshot-backgrounds/style-a/ · 5 张 Editorial 底 dune / forest-ink / indigo-porcelain / kraft-paper / monocle-classic - assets/screenshot-backgrounds/style-b/ · 4 张 Swiss 底 ikb-dot-gradient / lemon-green-dot-shadow / lemon-grid / safety-orange-halftone 模板 - template-editorial-card.html: 加 5 条 .frame-shot.bg-asset-* 规则 - template-swiss-card.html: 加 4 条 .frame-shot.bg-asset-* 规则 两侧都带 fallback 实色,素材未加载时背景不透 文档 - screenshot-treatment.md: 新增 Solid vs Asset 小节 + 两张对照表 + 4 条 rules of thumb + 两条新 cheat-sheet recipe,明确路径假设与 accent 绑定 - HANDOFF.md: 版本号 v0.14,目录树加 screenshot-backgrounds/, 写清"素材迁移不是规则迁移"的边界(未引入 PPT 原 7 参数语义系统) 顺带归集本轮工作期间累积的散落改动(PRODUCT/SKILL/category-cookbook/ components/content-planning/map-component/production-workflow/ qa-checklist/style-system/validate-social-deck)一并入库,避免长期挂在 dirty tree。
No files matched your search
@@ -1,6 +1,6 @@
|
||||
# Handoff — guizang-social-card-skill
|
||||
|
||||
最后更新:2026-05-27 · 版本:v0.13(git 初始化 + 仓库瘦身:本地测试归集到 `local-tests/` 并 gitignore)
|
||||
最后更新:2026-05-28 · 版本:v0.14(截图美化素材迁移:9 张 WebP 背景从 PPT skill port 过来 + 两个种子加 9 条 `.bg-asset-*`)
|
||||
|
||||
这份文档只记录**事实**:当前 skill 由哪些文件组成、每个文件管什么、本轮更新改了什么、怎么验证它还能跑。想读"为什么这样做"或"未来要怎么走",去看 `PRODUCT.md`。
|
||||
|
||||
@@ -12,26 +12,32 @@
|
||||
|
||||
```
|
||||
guizang-social-card-skill/
|
||||
├── SKILL.md # 266 行 · 7 步工作流入口
|
||||
├── SKILL.md # 7 步工作流入口
|
||||
├── HANDOFF.md # 本文件
|
||||
├── PRODUCT.md # 产品思考
|
||||
├── assets/
|
||||
│ ├── template-editorial-card.html # 585 行 · 杂志风种子
|
||||
│ ├── template-swiss-card.html # 740 行 · 瑞士风种子(v0.6 已硬化)
|
||||
│ └── magazine-bg-webgl.js # WebGL 墨流背景
|
||||
├── references/ # 12 份子规范,按需读取
|
||||
│ ├── platform-specs.md (122) 平台 × 分辨率 × 命名
|
||||
│ ├── style-system.md (182) 两种风格的硬规则与反模式
|
||||
│ ├── theme-presets.md (212) 6 套杂志 palette(含 1 套暗色 Midnight Ink)+ 4 套 Swiss accent
|
||||
│ ├── layout-recipes.md (667) 27 个版式骨架(M01-M15 + S01-S12)
|
||||
│ ├── components.md (226) 字体 / 卡片 / 间距 / 图标 / 标题字数表
|
||||
│ ├── background-systems.md (108) 墨流 / 网格 / 纸纹层
|
||||
│ ├── portrait-fill.md (85) 3:4 板的留白对策
|
||||
│ ├── content-planning.md (138) 钩子 / 拆页 / 文案压缩
|
||||
│ ├── production-workflow.md (172) 渲染管线与命令
|
||||
│ ├── image-overlay.md (150) 文字压图:蒙版 + 主体映射
|
||||
│ ├── category-cookbook.md (232) 小红书 11 个品类的版式路由
|
||||
│ └── qa-checklist.md (67) 交付前 checklist
|
||||
│ ├── 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 条规则的渲染后校验器
|
||||
@@ -71,7 +77,28 @@ v0.13 起:git 仓库。`local-tests/` 整目录在 `.gitignore` 中,commit
|
||||
|
||||
仅记录"什么时候新增/变更了哪些可见资产",不记录文风调整。
|
||||
|
||||
### v0.13 · 2026-05-27(本轮)
|
||||
### 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.webp`
|
||||
- `style-b/` 4 张 Swiss:`ikb-dot-gradient.webp` / `lemon-green-dot-shadow.webp` / `lemon-grid.webp` / `safety-orange-halftone.webp`
|
||||
- `assets/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` 加 "Solid `bg-*` vs asset `bg-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` 高比例板仍优先用 solid `bg-*`,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` 与锁文件。
|
||||
|
||||
@@ -83,7 +110,7 @@ v0.13 起:git 仓库。`local-tests/` 整目录在 `.gitignore` 中,commit
|
||||
|
||||
**重要约定**:`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(上一轮)
|
||||
### 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` 不合规的问题。
|
||||
|
||||
@@ -349,13 +376,13 @@ sips -g pixelWidth -g pixelHeight output/xhs-01.png # 应为 1080×1440
|
||||
- **Step 3 不要混风格**:Editorial 用 serif + ledger/marginalia;Swiss 用 Inter + card-fill/matrix。两套 class 命名空间隔离,模板间不能跨用。
|
||||
- **Step 4.5 必须拷种子**:从零写 HTML 是 v0.4 之前的工作流,已废弃。
|
||||
|
||||
文字压图的页(封面 / 大图位 / 全幅图)三步:先 `Read` 图、再写 HTML 注释记录主体位置、再选 `object-position`。蒙版是强制的。
|
||||
文字压图的页(封面 / 大图位 / 全幅图)四步:先 `Read` 图、写 HTML 注释记录主体位置、选 `object-position`、做 360px 缩略图检查。默认先不加遮罩;只有缩略图不可读时才加局部、取自图片色调的 tint。
|
||||
|
||||
---
|
||||
|
||||
## 6. 已知风险与限制(事实层面)
|
||||
|
||||
- **没有自动校验脚本**。所有"标题超过几个字会溢出"目前靠 components.md 的硬上限表 + 人工渲染 + 视觉判断。
|
||||
- **校验脚本是按需兜底**。`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`)。
|
||||
|
||||
@@ -177,7 +177,7 @@ v0.6-v0.11 都把 validator(`validate-social-deck.mjs`)写成"渲完必须
|
||||
2. **重字号 + 重字重的标题**:Swiss 80-120px 配 weight 700 → 拒绝。"越大越细"是 Swiss 的内核。
|
||||
3. **任意装饰 SVG**:blob、雨滴、kawaii 贴纸、随手画的圈 → 拒绝。
|
||||
4. **嵌套卡片 / SaaS 拼盘**:默认布局不应该是"卡片里再放卡片" → 拒绝。
|
||||
5. **裸跑图片压字**:照片占 ≥60% 画面但没加蒙版 → 拒绝。
|
||||
5. **裸跑图片压字**:照片没有 quiet zone、标题压过主体,或缩略图不可读 → 拒绝;局部 tint 只是兜底,不是默认遮罩。
|
||||
6. **假数据 / 假版本号 / 假报价** → 拒绝。
|
||||
7. **一图复用到所有比例**:把 21:9 直接裁成 1:1 → 拒绝。两个比例必须分别构图。
|
||||
8. **图上加教程说明**:键盘快捷键、操作步骤压在卡上 → 拒绝(教程内容写在 caption / 文案区)。
|
||||
@@ -194,19 +194,20 @@ v0.6-v0.11 都把 validator(`validate-social-deck.mjs`)写成"渲完必须
|
||||
|
||||
1. **Editorial 模板的冒烟测试**。v0.6 只在 Swiss 上跑了真实小例子,Editorial 模板尚未做同样的硬化。预计会暴露 M14 纵向 Pipeline 5 步在 3:4 上的溢出,以及 M11 Marginalia Essay 的字号上限。下一轮优先做。
|
||||
|
||||
2. **校验脚本 `validate-social-deck.mjs`**。在 Step 5 渲染后自动跑:
|
||||
- 检查每张图的 `scrollHeight` 是否超过 board 高度(v0.6 用 Playwright `evaluate` 验证过这个方法)
|
||||
2. **校验脚本 `validate-social-deck.mjs`**。脚本已完成,默认作为用户要求 auto-check 时的兜底:
|
||||
- 检查每张图的 `scrollHeight` 是否超过 board 高度
|
||||
- 检查正文字号是否低于平台最小值
|
||||
- 检查 `.poster.xhs` 上的 `.h-xl` 是否超过硬上限
|
||||
把 "渲染前看不出来 / 渲染后才发现溢出" 这种坑用脚本兜底。
|
||||
- 后续可补媒体左对齐 / browser-default margin 这类视觉漂移检查
|
||||
把 "渲染前看不出来 / 渲染后才发现溢出" 这种坑用脚本兜底,同时不阻塞用户先看图。
|
||||
|
||||
3. **一次出多平台的"包"**。当前一篇文章如果同时要发小红书 + 公众号,要开两个任务文件夹各做一遍。可以做一个 `deck` 概念:一份 source content → 同时产出 **小红书 5-9 张 3:4 组图** + **公众号封面对(21:9 主图 + 1:1 方图,同一封面的两个尺寸)** → 一次渲染出全部 PNG。
|
||||
关键设计点:避免把 21:9 内容直接缩到 1:1(产品决策 7 已禁止)。需要在 plan 阶段就为 21:9 写长标题、为 1:1 写一版短标题。
|
||||
|
||||
4. **1:1 短标题生成器(公众号封面对的同源短标题)**。21:9 主图用 "一周的代码 · 我和五个 AI 工具的同居生活",1:1 方图用 "AI 同居生活"。注意这是**同一篇文章的封面对**,不是组图——21:9 有水平空间放长标题,1:1 没有所以必须压短。当前两个尺寸的标题靠 AI 自由发挥,可以加一个明确的"6-8 字短标题"约束在 Step 2 的 plan 阶段。
|
||||
|
||||
5. **地图组件**。旅行类内容经常需要"路线图 + 关键节点"。Mapbox 静态 API 出图能用,但目前要 AI 现写代码调用。可以做成一个 `.map-block` class,传 GeoJSON 进去自动出。
|
||||
**执行路径**:用 Mapbox Static Images API(免费额度够冒烟测试),出 1080×720 或 1080×1440 PNG,限制底图为 `light-v11` / `dark-v11` 两种以匹配 Editorial / Swiss 的中性基调,禁用花哨色板。节点用 Swiss 编号风(黑底白数字)或 Editorial 墨点风,避开默认蓝针。GeoJSON 仅接 LineString + Point,复杂面/标注超出范围。
|
||||
5. **地图组件**。旅行类内容经常需要"路线图 + 关键节点"。真实攻略地图默认用 Mapbox Static;没有 token 时用 OSM 静态瓦片;SVG schematic 只用于抽象关系图。
|
||||
**执行路径**:用 Mapbox Static Images API 或 OSM tile composite 出 1080×720 / 1080×1440 PNG,限制底图为中性浅色 / 暗色以匹配 Editorial / Swiss,禁用花哨色板。节点用 Swiss 编号风(黑底白数字)或 Editorial 墨点风,避开默认蓝针。GeoJSON 仅接 LineString + Point,复杂面/标注超出范围。
|
||||
**路由**:先接入 `category-cookbook.md` 的"旅行"路由,作为 M02 / S08 的可选 inset;不强推。
|
||||
|
||||
6. **截图美化资产(从 PPT skill 借现成的)**。Tutorial / News 类目最大的痛点是"截图本身不好看"——背景太杂、有阴影、文字太多。可以加一组工具:自动加圆角、自动模糊背景、自动裁安全区。
|
||||
|
||||
@@ -70,9 +70,9 @@ Read these files as needed:
|
||||
- `references/portrait-fill.md` when adapting layouts to 3:4 and avoiding under-filled vertical space.
|
||||
- `references/content-planning.md` for cover hooks, page breakdown, and copy compression.
|
||||
- `references/production-workflow.md` for HTML/CSS rendering and image handling.
|
||||
- `references/image-overlay.md` whenever text sits on top of a photo: contrast mask rules + face / subject avoidance via multimodal subject mapping.
|
||||
- `references/image-overlay.md` whenever text sits on top of a photo: photo qualification, localized tint fallback, and face / subject avoidance via multimodal subject mapping.
|
||||
- `references/screenshot-treatment.md` when the user supplies an app / web / code / dashboard screenshot — picks `.frame-shot` over `.frame-img`, sets corners/shadow/bg/inset, decides on `.device-browser` or `.device-phone` chrome.
|
||||
- `references/map-component.md` when the content has spatial relationships (travel route, store locations, walking tour) — picks `.map-block` mode S (schematic SVG, default) / T (Mapbox Static, requires token) / O (OSM, fallback), places pins, never uses live JS.
|
||||
- `references/map-component.md` when 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.md` when 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.md` to route a user-named Rednote category (旅行 / 职场 / 游戏 / 影视 / 彩妆 / 美食 / 穿搭 / 家居 / 健身 / 情感 / 推荐) to applicable recipes and to confirm scope.
|
||||
- `references/qa-checklist.md` before delivering final images.
|
||||
@@ -178,7 +178,7 @@ The seed already wires up: font loading, theme tokens, all three poster sizes (`
|
||||
|
||||
Set the theme/accent on the `<html>` element:
|
||||
|
||||
- Editorial: `<html data-theme="ink-classic | indigo-porcelain | forest-ink | kraft-paper | dune">`.
|
||||
- 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.
|
||||
@@ -189,11 +189,11 @@ 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. Only the `<!-- POSTERS_HERE -->` region should change page-to-page.
|
||||
- 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 `.poster` or `.cover` node.
|
||||
- Save rendered images in `output/`.
|
||||
- Verify dimensions and inspect the rendered PNGs.
|
||||
- Run `node validate-social-deck.mjs <task-dir>` after every render. It checks overflow (R1), footer collision (R2), Swiss bold display (R3), minimum font size (R4), 4-band density (R5), and `.h-xl` line caps (R6). Exit code 1 on any FAIL — fix before delivery. WARN is advisory but read it.
|
||||
- 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-xl` line 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.
|
||||
|
||||
@@ -212,10 +212,10 @@ When the user provides screenshots:
|
||||
|
||||
Whenever a poster places text on top of a photo (full-bleed cover, large image well, generated-image overlay), follow `references/image-overlay.md`:
|
||||
|
||||
- **Mask is mandatory.** A photo covering ≥60% of the canvas needs a contrast mask layer (vertical falloff / side scrim / radial vignette). Never rely on the photo's own brightness.
|
||||
- **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 in `references/components.md` for ranges and `image-overlay.md` for 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, the mask is too thin; if the photo looks dead, the mask is too heavy.
|
||||
- **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).
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 232 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 250 KiB |
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 377 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 249 KiB |
@@ -141,6 +141,7 @@
|
||||
.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; }
|
||||
@@ -419,6 +420,14 @@
|
||||
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; }
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
.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); }
|
||||
@@ -410,6 +411,13 @@
|
||||
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; }
|
||||
|
||||
@@ -11,7 +11,7 @@ Per-category cheat sheet for the 11 most common Rednote (小红书) content type
|
||||
|
||||
This is a routing document. When the user names a category, find the row here and start from the listed recipes instead of building from a blank file.
|
||||
|
||||
For any category, if a poster has a full-bleed image with text on top, also follow `image-overlay.md` (mask + subject avoidance).
|
||||
For any category, if a poster has a full-bleed image with text on top, also follow `image-overlay.md` (photo qualification, localized tint fallback, and subject avoidance).
|
||||
|
||||
---
|
||||
|
||||
@@ -21,7 +21,7 @@ For any category, if a poster has a full-bleed image with text on top, also foll
|
||||
|
||||
- **Recipes**: M01 (text-led cover, no photo dominance) **or M16 (image-led cover, when the user has 1+ great photos)**, M02 (field-note photo), M11 (marginalia essay), M07 (closing note), S11 (itinerary ledger). For image-heavy submissions use the M16 → S11/M05 → M02 sequence in `references/content-planning.md`.
|
||||
- **Style mode**: Editorial × kraft-paper / dune (warm-tone destinations) or × forest-ink (mountain/wilderness). Swiss × IKB works for "trip data report" style.
|
||||
- **Text scheme**: Text beside image is the default. Cover uses text-on-image with a vertical falloff mask. Body pages use photo + caption pairs (field-note style).
|
||||
- **Text scheme**: Text beside image is the default. Cover can use text-on-image only when the photo has a quiet zone; add localized tint only if the thumbnail check fails. Body pages use photo + caption pairs (field-note style).
|
||||
- **Image source**: User photos > Pexels (for China destinations — supports Chinese keyword search) > Unsplash (`/s/photos/<destination>`, best for overseas / English keywords) > Flickr CC (`license=2,3,4,5,6,9`, when you need documentary "real trip" feel rather than postcard polish). Always log to `assets/SOURCES.md`.
|
||||
- **Content shape**: 5-7 pages. Cover (destination + dates) → atmosphere photo + lead → itinerary ledger → 2-3 field notes (one location each) → closing quote / next-stop teaser.
|
||||
- **Pitfalls**: Generic "best places in X city" listicle voice. Cure: keep one specific date / weather / mileage detail per page to anchor it as observed, not researched.
|
||||
@@ -52,7 +52,7 @@ See: `local-tests/demo-image-01-yading/` for a reference build.
|
||||
|
||||
- **Recipes**: M01 (cover with full-bleed art), M08 (boss tier ledger), S07 (takeaway ledger), S11 (chapter timeline), M15 (build before/after).
|
||||
- **Style mode**: Editorial dark (ink-classic with paper inverted to near-black) for atmospheric games (黑神话 / Elden Ring). Swiss for esports / competitive data ("胜率 / KDA / 出装").
|
||||
- **Text scheme**: Text-on-image is standard for game covers because game art is the primary draw. Mask is mandatory (see `image-overlay.md` Rule 1A vertical falloff).
|
||||
- **Text scheme**: Text-on-image is standard for game covers because game art is the primary draw. Use subject mapping and thumbnail checks from `image-overlay.md`; add a localized, image-toned tint only where the title needs support.
|
||||
- **Image source**: Wallhaven JSON API (see SKILL.md Step 6) for keyword pulls, official screenshots for specific moments. Always disclose copyright risk and log to `SOURCES.md`. If user opts out of attribution, do not crop the credit out of the image itself.
|
||||
- **Content shape**: 4-6 pages. Cover (game name + playtime) → first impression page → chapter-by-chapter ledger → memorable boss / scene page → verdict.
|
||||
- **Pitfalls**:
|
||||
|
||||
@@ -8,7 +8,7 @@ Editorial Magazine x E-ink (`template-editorial-card.html`):
|
||||
|
||||
- `--serif-zh`: Noto Serif SC, Songti SC, STSong — display titles.
|
||||
- `--serif-en`: Playfair Display, italic for subtitles and pull quotes.
|
||||
- `--sans-zh`: Noto Sans SC, PingFang SC — body and lead.
|
||||
- `--sans-zh`: Noto Sans SC, PingFang SC — utility text and fallback; body and lead default to serif-zh in Editorial.
|
||||
- `--sans-en`: Inter — Latin body in mixed content.
|
||||
- `--mono`: IBM Plex Mono — labels, page metadata, issue strip, captions.
|
||||
|
||||
@@ -87,11 +87,11 @@ The seed template ships these caps. Anything more violates the vertical budget
|
||||
|
||||
### Editorial `.h-xl` — Hard Caps per Board (validated)
|
||||
|
||||
The Editorial seed default for `.h-xl` is 92px serif weight 700. Verified in `local-tests/demo-smoke-editorial-travel/` on M11/M14/M07 with 3-paragraph body + ledger + 5-step pipeline:
|
||||
The Editorial seed default for `.h-xl` is 88px serif weight 500. Verified in `local-tests/demo-smoke-editorial-travel/` on M11/M14/M07 with 3-paragraph body + ledger + 5-step pipeline:
|
||||
|
||||
| Board | Default `.h-xl` | Max lines | Max chars / line | What happens past the cap |
|
||||
| -------------------- | --------------- | --------- | ---------------- | ------------------------------------------------------------------------- |
|
||||
| `.poster.xhs` (1080×1440) | 92px | 2 | 9 | 3 lines push the marginalia / ledger / pipeline rows past the issue strip |
|
||||
| `.poster.xhs` (1080×1440) | 88px | 2 | 9 | 3 lines push the marginalia / ledger / pipeline rows past the issue strip |
|
||||
| `.poster.square` (1080×1080) | 78px | 2 | 8 | Sub-paragraph or pull-quote clips |
|
||||
| `.poster.wide` (2100×900) | 96px | 1 | 16 | Wraps and crowds the marginalia column |
|
||||
|
||||
@@ -189,11 +189,11 @@ Two device wrappers (`.device-browser`, `.device-phone`) ship in both templates
|
||||
|
||||
## Map Block (.map-block)
|
||||
|
||||
For content with spatial relationships (routes, neighborhoods, store locations). Both seeds ship `.map-block` + `.map-pin` + `.map-legend`. Three modes — see `references/map-component.md` for the full playbook:
|
||||
For content with spatial relationships (routes, neighborhoods, store locations). Both seeds ship `.map-block` + `.map-pin` + `.map-legend`. Pick the map source by how literal the location needs to be — see `references/map-component.md` for the full playbook:
|
||||
|
||||
- **Mode S** (default) — schematic SVG drawn in `viewBox="0 0 100 100"`, no token, hand-illustrated feel.
|
||||
- **Mode T** — Mapbox Static Images API URL as `<img>` source; needs `MAPBOX_ACCESS_TOKEN`. Apply `tone-paper` (Editorial) or `tone-paper` saturation-0 (Swiss).
|
||||
- **Mode O** — OSM tile composite, fallback when no token.
|
||||
- **Mode T** (default for travel / route / wayfinding) — Mapbox Static Images API URL as `<img>` source; needs `MAPBOX_ACCESS_TOKEN`. Apply `tone-paper` (Editorial) or `tone-paper` saturation-0 (Swiss).
|
||||
- **Mode O** — OSM static tile composite, fallback when no Mapbox token is available and the map still needs to be geographically real.
|
||||
- **Mode S** — schematic SVG drawn in `viewBox="0 0 100 100"` for conceptual or abstract relation maps only.
|
||||
|
||||
Hard limits: ≤6 pins per board, ≤1 accent pin, no SVG text labels (names go in `.map-pin .card`).
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ When this pattern does NOT apply:
|
||||
- Posts with one good photo only — use M16 for P1, then M02 once on P3-4 with that same photo or a different angle. Do not pad.
|
||||
- Swiss-mode product/release posts — photos in Swiss are product renders or UI screenshots, not lifestyle. Use S08 Image Hero, not M16.
|
||||
|
||||
For the cover title placement and typography on a great photo, see `references/layout-recipes.md` M16 (4 modes: 顶压底沉 / 侧栏立柱 / 角落徽章 / 下沉条带) and `references/image-overlay.md` (subject map + mask).
|
||||
For the cover title placement and typography on a great photo, see `references/layout-recipes.md` M16 (4 modes: 顶压底沉 / 侧栏立柱 / 角落徽章 / 下沉条带) and `references/image-overlay.md` (subject map + localized tint fallback).
|
||||
|
||||
## Page Count Guidance
|
||||
|
||||
|
||||
@@ -8,7 +8,50 @@ Both seeds ship `.map-block` + `.map-pin` + `.map-legend` classes. The same mark
|
||||
|
||||
Pick mode **before** writing the markup. They are not stackable.
|
||||
|
||||
### Mode S · Schematic SVG (default, no token)
|
||||
Default selection:
|
||||
|
||||
- Travel guides, walking routes, hiking, store locations, or anything the reader may use for orientation → **Mode T** Mapbox Static.
|
||||
- Same need, but no Mapbox token → **Mode O** OSM static tile composite.
|
||||
- Conceptual relationship map, fictional route, or purely editorial "places are connected like this" graphic → **Mode S** schematic SVG.
|
||||
|
||||
### Mode T · Mapbox Static Image (default for real routes)
|
||||
|
||||
Real raster tiles, desaturated and tinted to match the deck. Use when the content is travel / hiking / neighborhood wayfinding and you need to show actual terrain or streets. Requires `MAPBOX_ACCESS_TOKEN` in the build env.
|
||||
|
||||
```
|
||||
https://api.mapbox.com/styles/v1/mapbox/light-v11/static/
|
||||
pin-s-1+555555(116.404,39.915),
|
||||
pin-l-2+1936b3(116.421,39.918)
|
||||
/116.412,39.916,14,0/1200x675@2x
|
||||
?access_token=YOUR_TOKEN
|
||||
```
|
||||
|
||||
```html
|
||||
<div class="map-block r-16x10 tone-paper">
|
||||
<img src="https://api.mapbox.com/styles/v1/mapbox/light-v11/static/.../1200x675@2x?access_token=..." alt="Lhasa walking route">
|
||||
|
||||
<!-- pins overlay (calculate % from mapbox bounds) -->
|
||||
<div class="map-pin" style="left:34%; top:52%;">...</div>
|
||||
<div class="map-pin accent" style="left:64%; top:42%;">...</div>
|
||||
|
||||
<div class="map-legend">LHASA · MAPBOX LIGHT</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Editorial uses `tone-paper` (multiply-blend, saturation 36%). Swiss uses `tone-paper` with saturation 0 (pure greyscale). Don't show the raw Mapbox colour-saturated style — it fights the deck.
|
||||
|
||||
**Style allowlist** for the URL — keep it monochrome:
|
||||
- `mapbox/light-v11`
|
||||
- `mapbox/dark-v11`
|
||||
- `mapbox/streets-v12` (only with `tone-paper`, never raw)
|
||||
|
||||
Never use `outdoors-v12` or `satellite-v9` — too colourful, will clash with both styles.
|
||||
|
||||
### Mode O · OpenStreetMap tile composite (fallback for real routes)
|
||||
|
||||
Free, no token. Use this when a real map is needed but Mapbox is unavailable. Render server-side via `staticmaps` npm package or similar, then place the raster as the `.map-block > img` child. Quality is lower than Mapbox, so keep labels sparse and let HTML pins carry the useful text.
|
||||
|
||||
### Mode S · Schematic SVG (conceptual / illustrative only)
|
||||
|
||||
Hand-drawn-style vector map drawn into a `viewBox="0 0 100 100"` SVG. Best for editorial decks where the map should feel like an illustrated guide, not a satellite capture. No external service.
|
||||
|
||||
@@ -49,46 +92,9 @@ Layer order in the SVG (back to front):
|
||||
|
||||
Pins sit *outside* the SVG, as HTML siblings — they get the `.frame-shot` treatment for free.
|
||||
|
||||
### Mode T · Mapbox Static Image (signed token)
|
||||
|
||||
Real raster tiles, desaturated and tinted to match the deck. Use when the content is travel / hiking and you need to show actual terrain. Requires `MAPBOX_ACCESS_TOKEN` in the build env.
|
||||
|
||||
```
|
||||
https://api.mapbox.com/styles/v1/mapbox/light-v11/static/
|
||||
pin-s-1+555555(116.404,39.915),
|
||||
pin-l-2+1936b3(116.421,39.918)
|
||||
/116.412,39.916,14,0/1200x675@2x
|
||||
?access_token=YOUR_TOKEN
|
||||
```
|
||||
|
||||
```html
|
||||
<div class="map-block r-16x10 tone-paper">
|
||||
<img src="https://api.mapbox.com/styles/v1/mapbox/light-v11/static/.../1200x675@2x?access_token=..." alt="Lhasa walking route">
|
||||
|
||||
<!-- pins overlay (calculate % from mapbox bounds) -->
|
||||
<div class="map-pin" style="left:34%; top:52%;">...</div>
|
||||
<div class="map-pin accent" style="left:64%; top:42%;">...</div>
|
||||
|
||||
<div class="map-legend">LHASA · MAPBOX LIGHT</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Editorial uses `tone-paper` (multiply-blend, saturation 36%). Swiss uses `tone-paper` with saturation 0 (pure greyscale). Don't show the raw Mapbox colour-saturated style — it fights the deck.
|
||||
|
||||
**Style allowlist** for the URL — keep it monochrome:
|
||||
- `mapbox/light-v11`
|
||||
- `mapbox/dark-v11`
|
||||
- `mapbox/streets-v12` (only with `tone-paper`, never raw)
|
||||
|
||||
Never use `outdoors-v12` or `satellite-v9` — too colourful, will clash with both styles.
|
||||
|
||||
### Mode O · OpenStreetMap tile composite (fallback)
|
||||
|
||||
Free, no token. Reach for this only if you don't have a Mapbox token and the schematic doesn't carry the message. Render server-side via `staticmaps` npm package or similar — not covered here. Quality is lower than Mapbox.
|
||||
|
||||
## Hard rules
|
||||
|
||||
- **No pin labels on the SVG**. Names live in `.map-pin .card`, never as SVG `<text>`. Keeps font rendering consistent and avoids vector-text scaling issues.
|
||||
- **No pin labels on the SVG or raster tile.** Names live in `.map-pin .card`, never as SVG `<text>` or baked into the map image. Keeps font rendering consistent and avoids scaling issues.
|
||||
- **Max 6 pins per board**. More than that, the cards collide. Use a relations / index card to list secondary points.
|
||||
- **One accent pin maximum**. Multiple accents kill the visual hierarchy.
|
||||
- **Pins must not overlap cards**. Test in browser first — if two cards are within 80 px on the long axis, alternate `.left` placement.
|
||||
@@ -136,7 +142,7 @@ Tone only affects `> img` children, not the SVG schematic.
|
||||
|
||||
## Common mistakes
|
||||
|
||||
- **Pasting Google Maps screenshots** — wrong tone, has UI chrome, will look out of place. Use Mapbox Static or schematic.
|
||||
- **Pasting Google Maps screenshots** — wrong tone, has UI chrome, will look out of place. Use Mapbox Static, OSM static tiles, or a schematic when the map is conceptual.
|
||||
- **Tracing a real map into SVG** — schematic should *abstract*, not replicate. If a viewer can guess the city from your SVG alone, you over-traced.
|
||||
- **Tiny pin cards** — `.card .name` is 15-16 px minimum. Cut the name if it overflows; never shrink.
|
||||
- **Accent on every pin** — defeats the highlight system. Pick one most-important point.
|
||||
@@ -131,9 +131,11 @@ Programmatic framing is preferred for user-provided screenshots:
|
||||
|
||||
- Create a clean target-ratio frame.
|
||||
- Add plain white, refined grey, or paper background. Do not add page-wide grid/dot backgrounds unless the user explicitly asks for a technical blueprint look.
|
||||
- If the capture contains a floating window/card over unrelated UI, crop to the foreground subject before placing it.
|
||||
- Place screenshot inside with safe padding.
|
||||
- Preserve readable text.
|
||||
- Do not redraw the screenshot unless the user asked for redesign.
|
||||
- Do not add perspective, skew, rotation, or mockup tilt unless the user explicitly asks for a scene mockup.
|
||||
|
||||
For Swiss:
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ If failing: don't shrink the canvas, don't add decorative blobs. Either expand c
|
||||
|
||||
Run these only for posters where text touches a photo (full-bleed cover, large image well, generated overlay). See `references/image-overlay.md`.
|
||||
|
||||
- Image area ≥60% of canvas → A, B, or C contrast mask is present (not a flat black rectangle behind text).
|
||||
- Image area ≥60% of canvas → the photo passes the quiet-zone + light tests; no-mask composition was tried first; any tint is localized, image-toned, and only used if the thumbnail check fails.
|
||||
- Subject map is documented as an HTML comment next to the hero block (face / focal feature location + safe zones).
|
||||
- No display title (≥72 px) overlaps a face, hand, or key product feature.
|
||||
- `object-position` was chosen from the subject-location table, not left at default for face shots.
|
||||
|
||||
@@ -11,6 +11,12 @@ When the user supplies an app / web / code / dashboard screenshot, **do not** dr
|
||||
|
||||
For **photographic** content (people, scenery, products) — keep using `.frame-img`. The treatments below assume a pixel-perfect UI source where contain-fit is non-negotiable.
|
||||
|
||||
## Subject Prep
|
||||
|
||||
Before framing, decide what the actual screenshot subject is. If the source capture contains a floating modal/card over an unrelated page, desktop chrome, cropped side text, cursor trails, notification fragments, or leftover background UI, crop to the foreground window/card first and then place that cleaned subject into the stage. Do not beautify the entire raw capture when it carries accidental text or partial UI behind the subject.
|
||||
|
||||
Screenshot beautification must not introduce perspective, skew, rotation, or 3D tilt unless the user explicitly asks for a mockup scene. A CleanShot-style treatment is orthographic: straight subject, equal scaling, quiet background, and clear safe padding.
|
||||
|
||||
## Anatomy
|
||||
|
||||
```
|
||||
@@ -71,6 +77,38 @@ Never go above 14 px — anything bigger reads as iOS marketing.
|
||||
|
||||
Backgrounds are **never** accent-coloured. If the screenshot needs an accent emphasis, add a `.t-cat` chip or `.kicker` next to it — don't tint the stage.
|
||||
|
||||
#### Solid `bg-*` vs asset `bg-asset-*`
|
||||
|
||||
The tokens above are **CSS-generated solids** — flat tones, fast, no asset dependency. They work, but for code / IDE / dashboard / dense-UI captures they can feel undersold. Ported from the PPT skill, both seed templates also ship 9 **real texture WebP** backgrounds under `assets/screenshot-backgrounds/`. Reach for these when the screenshot is the page's hero, when a solid stage feels too thin, or when you need the warmth/grain of a printed magazine.
|
||||
|
||||
Editorial assets (`assets/screenshot-backgrounds/style-a/`):
|
||||
|
||||
| Class | Tone | Best for |
|
||||
| --------------------------- | ------------------ | ------------------------------------------------- |
|
||||
| `bg-asset-dune` | Warm sand | Travel / outdoor app shots, lifestyle products |
|
||||
| `bg-asset-forest-ink` | Deep forest green | Dark-mode UI on Editorial — pairs with `ink-classic` |
|
||||
| `bg-asset-indigo-porcelain` | Cool porcelain | Reading / writing apps, design tools |
|
||||
| `bg-asset-kraft-paper` | Kraft brown | Notes / journaling / handwritten apps |
|
||||
| `bg-asset-monocle-classic` | Cream paper | Default warm hero — most magazine-like |
|
||||
|
||||
Swiss assets (`assets/screenshot-backgrounds/style-b/`):
|
||||
|
||||
| Class | Accent | Use only when current accent matches |
|
||||
| -------------------------- | ----------------- | ------------------------------------------------ |
|
||||
| `bg-asset-ikb-dot` | IKB Klein Blue | `data-accent="ikb"` decks only |
|
||||
| `bg-asset-lemon-green-dot` | Lemon-green | `data-accent="lemon-green"` decks only |
|
||||
| `bg-asset-lemon-grid` | Lemon-yellow | `data-accent="lemon"` decks only |
|
||||
| `bg-asset-safety-orange` | Safety orange | `data-accent="safety-orange"` decks only |
|
||||
|
||||
**Rules of thumb**
|
||||
|
||||
- Don't mix accents — a `data-accent="ikb"` deck must not pull a `bg-asset-safety-orange` stage.
|
||||
- Asset backgrounds already carry texture; don't stack `shadow-ed` on top (the `1px` outline will read as a SaaS frame). Use `shadow-soft` or `shadow-none`.
|
||||
- Decide once per deck whether asset stages are part of the visual identity — don't sprinkle one asset background into a deck of solid stages, it looks like a sample card.
|
||||
- These assets are crop-safe at 16:10 and 16:9. For tall `r-3x4` or square `r-1x1` slots, prefer solid `bg-*` — the texture pattern can read awkwardly when over-cropped.
|
||||
|
||||
**Path note**: the `.bg-asset-*` rules use `url("../assets/screenshot-backgrounds/...")` — this assumes your deck's `index.html` lives one directory below `assets/`. If you place the deck elsewhere, override these rules locally with the correct relative path.
|
||||
|
||||
### 5. `inset-*` (padding between shot and stage)
|
||||
|
||||
- `inset-none` — image fills the frame. Use when the screenshot itself already has window chrome.
|
||||
@@ -139,6 +177,16 @@ Two recipes that cover 80% of cases.
|
||||
.frame-shot.r-16x10.corners-sm.shadow-soft.bg-paper-2.inset-sub
|
||||
```
|
||||
|
||||
**Editorial hero with real texture** — magazine-grade stage:
|
||||
```
|
||||
.frame-shot.r-16x10.corners-sm.shadow-soft.bg-asset-monocle-classic.inset-bal
|
||||
```
|
||||
|
||||
**Swiss hero with brand-aligned stage** — only when accent matches asset:
|
||||
```
|
||||
.frame-shot.r-16x10.corners-sq.shadow-none.bg-asset-ikb-dot.inset-bal
|
||||
```
|
||||
|
||||
For comparison shots (before / after), use **the same parameters** on both — different treatment between cells reads as inconsistency, not contrast.
|
||||
|
||||
## Validator
|
||||
|
||||
@@ -180,9 +180,9 @@ Both pass HTML linting; both fail the thumbnail readability test.
|
||||
|
||||
Fix by following `image-overlay.md`:
|
||||
|
||||
1. **Add a mask layer** appropriate to where text lands — vertical falloff (top+bottom), side scrim (one column), or radial vignette (text fills the canvas).
|
||||
1. **Qualify the photo first.** It needs a low-detail quiet zone and restrained light. If it fails, switch to a framed-photo recipe instead of forcing text on top.
|
||||
2. **Map the subject before placing the title.** Read the image, describe in plain language where the face/focal feature sits, record the subject map as an HTML comment next to the hero block. Place text only in documented safe zones.
|
||||
3. **Match `object-position` to subject location** — see the table in `image-overlay.md`. Never leave it at default for face / hero shots.
|
||||
4. **Thumbnail test** — render the PNG, downscale to 360 px wide, and look at the title. If it fights the photo, the mask is too thin; if the photo looks dead, the mask is too heavy.
|
||||
4. **Thumbnail test** — render the PNG, downscale to 360 px wide, and look at the title. If it fights the photo, add a localized, image-toned tint only around the title area; if the photo looks dead, the tint is too heavy or the photo is wrong.
|
||||
|
||||
This anti-pattern survives almost every other check because the HTML is valid, the image loads, and the title renders. The only way to catch it is to look at the rendered output at the size readers will actually see it.
|
||||
@@ -14,6 +14,7 @@
|
||||
* R4 min readable font body/lead/caption/label/meta below the mobile-safe floor
|
||||
* R5 4-band density on 3:4 boards: <75% filled OR any under-filled band > 216px
|
||||
* R6 h-xl hard cap display title lines/chars exceed the per-board cap
|
||||
* R7 figure margin drift browser-default <figure> margin offsets media alignment
|
||||
*/
|
||||
import { chromium } from "playwright";
|
||||
import { fileURLToPath } from "node:url";
|
||||
@@ -210,6 +211,29 @@ for (const s of sections) {
|
||||
warns.push({ rule: "R4", msg: `${t.role} "${t.text}" at ${t.size}px < ${t.min}px floor`, fix: "cut copy instead of shrinking type (components.md Minimum Readable Sizes)" });
|
||||
}
|
||||
|
||||
// R7 figure margin drift — catches browser-default 40px figure margins on custom media blocks.
|
||||
const figureDrift = await s.evaluate(el => {
|
||||
const out = [];
|
||||
for (const fig of el.querySelectorAll("figure")) {
|
||||
const cs = getComputedStyle(fig);
|
||||
const ml = parseFloat(cs.marginLeft) || 0;
|
||||
const mr = parseFloat(cs.marginRight) || 0;
|
||||
if (ml >= 16 || mr >= 16) {
|
||||
const text = fig.textContent.trim().replace(/\s+/g, " ").slice(0, 32);
|
||||
out.push({
|
||||
cls: fig.className ? "." + fig.className.split(" ").filter(Boolean).join(".") : "figure",
|
||||
ml: Math.round(ml),
|
||||
mr: Math.round(mr),
|
||||
text,
|
||||
});
|
||||
}
|
||||
}
|
||||
return out;
|
||||
});
|
||||
for (const f of figureDrift) {
|
||||
warns.push({ rule: "R7", msg: `${f.cls} has horizontal figure margin ${f.ml}px / ${f.mr}px${f.text ? ` near "${f.text}"` : ""}`, fix: "reset figure margins in the seed or task CSS: .poster figure { margin: 0; }" });
|
||||
}
|
||||
|
||||
// R5 4-band density (3:4 only)
|
||||
if (meta.board === "xhs") {
|
||||
const bands = await s.evaluate(el => {
|
||||
@@ -329,7 +353,7 @@ for (const { meta, fails, warns } of report) {
|
||||
}
|
||||
|
||||
lines.push("");
|
||||
lines.push(`Legend: R1 overflow · R2 footer collision · R3 swiss bold display · R4 min font · R5 4-band density · R6 h-xl cap`);
|
||||
lines.push(`Legend: R1 overflow · R2 footer collision · R3 swiss bold display · R4 min font · R5 4-band density · R6 h-xl cap · R7 figure margin drift`);
|
||||
lines.push(`Exit code 1 only on FAIL. Warnings are advisory.`);
|
||||
|
||||
console.log(lines.join("\n"));
|
||||
|
||||