完善 SVGlide 视觉生成与预检门禁

新增 style presets、visual recipe 和 aesthetic review gate,并接入 lark-slides skill 执行链路。

扩展 create-svg 图片处理、协议说明与 svg_preflight 校验,补充对应测试。
This commit is contained in:
songtianyi.theo
2026-06-10 19:23:25 +08:00
parent f5502416c8
commit be8a67b894
15 changed files with 4133 additions and 69 deletions

View File

@@ -139,8 +139,10 @@ func TestSlidesCreateSVGExecuteCreatesSlidesInFileOrder(t *testing.T) {
t.Fatalf("slide_ids = %v, want [slide_1 slide_2]", data["slide_ids"])
}
assertSlideCreateBodyContains(t, slideStub1, testSVGlidePage1)
assertSlideCreateBodyContains(t, slideStub2, testSVGlidePage2)
assertSlideCreateBodyContains(t, slideStub1, `slide:contract-version="svglide-authoring-contract/v1"`)
assertSlideCreateBodyContains(t, slideStub1, `<rect slide:role="shape" x="80" y="80" width="320" height="180"/>`)
assertSlideCreateBodyContains(t, slideStub2, `slide:contract-version="svglide-authoring-contract/v1"`)
assertSlideCreateBodyContains(t, slideStub2, `<foreignObject slide:role="shape" slide:shape-type="text" x="80" y="80" width="320" height="80">`)
}
func TestSlidesCreateSVGPartialFailureIncludesRecoveryContext(t *testing.T) {

View File

@@ -15,7 +15,11 @@ import (
"github.com/larksuite/cli/shortcuts/common"
)
const maxSVGFileSizeBytes int64 = 2 * 1024 * 1024
const (
maxSVGFileSizeBytes int64 = 2 * 1024 * 1024
svglideSlideNS = "https://slides.bytedance.com/ns"
svglideContractVersion = "svglide-authoring-contract/v1"
)
type RewrittenSVGPage struct {
Content string
@@ -112,6 +116,11 @@ func readSVGFiles(runtime *common.RuntimeContext, paths []string) ([]string, err
return nil, output.ErrValidation("--file %s: SVG file is empty", path)
}
svg := string(data)
var normalizeErr error
svg, normalizeErr = ensureSVGlideRootContractVersion(svg, path)
if normalizeErr != nil {
return nil, normalizeErr
}
if err := validateSVGlideSVG(svg, path); err != nil {
return nil, err
}
@@ -130,18 +139,41 @@ func validateSVGlideSVG(svg, path string) error {
return output.ErrValidation("--file %s: root element must be non-namespaced <svg>", path)
}
attrs := svg[m[6]:m[7]]
if !hasXMLAttr(attrs, "xmlns:slide", "https://slides.bytedance.com/ns") {
return output.ErrValidation("--file %s: root <svg> must declare xmlns:slide=\"https://slides.bytedance.com/ns\"", path)
if !hasXMLAttr(attrs, "xmlns:slide", svglideSlideNS) {
return output.ErrValidation("--file %s: root <svg> must declare xmlns:slide=\"%s\"", path, svglideSlideNS)
}
if !hasXMLAttr(attrs, "slide:role", "slide") {
return output.ErrValidation("--file %s: root <svg> must include slide:role=\"slide\"", path)
}
if version := xmlAttrValue(attrs, "slide:contract-version"); version != svglideContractVersion {
return output.ErrValidation("--file %s: root <svg> must include slide:contract-version=\"%s\"", path, svglideContractVersion)
}
if svg[m[8]:m[9]] == "/>" {
return nil
}
return validateSVGlideChildren(svg[m[9]:], path)
}
func ensureSVGlideRootContractVersion(svg, path string) (string, error) {
m := svgRootOpenTagRegex.FindStringSubmatchIndex(svg)
if m == nil {
return svg, nil
}
tagName := svg[m[4]:m[5]]
if tagName != "svg" {
return svg, nil
}
attrs := svg[m[6]:m[7]]
version := xmlAttrValue(attrs, "slide:contract-version")
if version == svglideContractVersion {
return svg, nil
}
if strings.TrimSpace(version) != "" {
return "", output.ErrValidation("--file %s: root <svg> must include slide:contract-version=\"%s\"", path, svglideContractVersion)
}
return svg[:m[8]] + fmt.Sprintf(` slide:contract-version="%s"`, svglideContractVersion) + svg[m[8]:], nil
}
func hasXMLAttr(attrs, name, want string) bool {
return xmlAttrValue(attrs, name) == want
}

View File

@@ -92,6 +92,38 @@ func TestInjectSVGTransportAssetMetadataMergesExisting(t *testing.T) {
}
}
func TestEnsureSVGlideRootContractVersionInjectsMissingVersion(t *testing.T) {
t.Parallel()
in := `<?xml version="1.0"?><!DOCTYPE svg><!-- lead --><svg xmlns="http://www.w3.org/2000/svg" xmlns:slide="https://slides.bytedance.com/ns" slide:role="slide"><rect slide:role="shape" x="0" y="0" width="100" height="60"/></svg>`
got, err := ensureSVGlideRootContractVersion(in, "page.svg")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(got, `slide:contract-version="svglide-authoring-contract/v1"`) {
t.Fatalf("contract version missing after normalization: %s", got)
}
if strings.Index(got, `slide:contract-version`) > strings.Index(got, `><rect`) {
t.Fatalf("contract version should be injected on the root open tag: %s", got)
}
if err := validateSVGlideSVG(got, "page.svg"); err != nil {
t.Fatalf("normalized SVG should pass validation: %v", err)
}
}
func TestEnsureSVGlideRootContractVersionRejectsWrongVersion(t *testing.T) {
t.Parallel()
in := `<svg xmlns="http://www.w3.org/2000/svg" xmlns:slide="https://slides.bytedance.com/ns" slide:role="slide" slide:contract-version="old"><rect slide:role="shape" x="0" y="0" width="100" height="60"/></svg>`
_, err := ensureSVGlideRootContractVersion(in, "page.svg")
if err == nil {
t.Fatal("expected wrong contract-version to fail")
}
if !strings.Contains(err.Error(), `slide:contract-version="svglide-authoring-contract/v1"`) {
t.Fatalf("error = %v, want contract-version guidance", err)
}
}
func TestValidateSVGlideSVGRecursiveChildren(t *testing.T) {
t.Parallel()
@@ -251,7 +283,7 @@ func TestValidateSVGlideSVGRecursiveChildren(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := validateSVGlideSVG(tt.svg, "page.svg")
err := validateSVGlideSVG(withTestSVGlideContractVersion(tt.svg), "page.svg")
if tt.wantErr == "" {
if err != nil {
t.Fatalf("unexpected error: %v", err)
@@ -268,6 +300,13 @@ func TestValidateSVGlideSVGRecursiveChildren(t *testing.T) {
}
}
func withTestSVGlideContractVersion(svg string) string {
if strings.Contains(svg, `slide:contract-version=`) {
return svg
}
return strings.Replace(svg, `slide:role="slide"`, `slide:role="slide" slide:contract-version="svglide-authoring-contract/v1"`, 1)
}
func TestExtractSVGlideErrorJSON(t *testing.T) {
t.Parallel()

View File

@@ -1,7 +1,7 @@
---
name: lark-slides
version: 1.0.0
description: "飞书幻灯片:创建和编辑幻灯片。创建演示文稿、读取幻灯片内容、管理幻灯片页面(创建、删除、读取、局部替换)。当用户需要创建或编辑幻灯片、读取或修改单个页面时使用。当用户给出 doubao.com 的 /slides/ URL/token 时,也应直接使用本 skill不要因为域名不是飞书而回退到 WebFetch路由依据是 URL 路径模式和 token而不是域名。不负责云文档内容编辑走 lark-doc、云文档里的独立画板对象走 lark-whiteboard注意 slide 内嵌的流程图/架构图仍属本 skill、上传或下载普通文件走 lark-drive。"
version: 1.0.2
description: "飞书幻灯片:创建和编辑幻灯片,接口通过 XML/SVG 协议通信。创建演示文稿、读取幻灯片内容、管理幻灯片页面(创建、删除、读取、局部替换)。当用户需要创建或编辑幻灯片、读取或修改单个页面时使用。当用户给出 doubao.com 的 /slides/ URL/token 时,也应直接使用本 skill不要因为域名不是飞书而回退到 WebFetch路由依据是 URL 路径模式和 token而不是域名。不负责云文档内容编辑走 lark-doc、云文档里的独立画板对象走 lark-whiteboard注意 slide 内嵌的流程图/架构图仍属本 skill、上传或下载普通文件走 lark-drive。"
metadata:
requires:
bins: ["lark-cli"]
@@ -15,11 +15,11 @@ metadata:
| 用户需求 | 优先动作 | 关键文档 / 命令 |
|----------|----------|-----------------|
| 新建 PPT | 先规划 `slide_plan.json`,再按复杂度选择一步或两步创建 | `planning-layer.md``visual-planning.md``asset-planning.md``slides +create` |
| AI 生成 SVG 创建 PPT | 复用 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json` 规划,生成 SVGlide SVG 后调用 `slides +create-svg` | `lark-slides-create-svg.md``svg-protocol.md` |
| AI 生成 SVG 创建 PPT | 复用 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json` 规划,生成 SVGlide SVG 后调用 `slides +create-svg` | `lark-slides-create-svg.md``svg-protocol.md``svg-visual-recipes.md``svg-aesthetic-review.md` |
| 大幅改写页面 | 先回读现有 XML写入新 plan再替换或重建相关页面 | `xml_presentations.get``+replace-slide``lark-slides-edit-workflows.md` |
| 编辑单个标题、文本块、图片或局部元素 | 优先块级替换/插入,不改页序 | `slides +replace-slide``lark-slides-replace-slide.md` |
| 读取或分析已有 PPT | 解析 slides/wiki token回读全文或单页 XML保存 `xml_presentation_id``slide_id``revision_id` | `xml_presentations.get``xml_presentation.slide.get` |
| 上传或使用图片 | 先上传为 `file_token`,禁止直接写 http(s) 外链 | `slides +media-upload`,或 `+create --slides``@./path` 占位符 |
| 上传或使用图片 | Preview 阶段优先多用真实图片增强视觉冲击;可先用公开可访问 http(s)/data 图片或本地 `@./path`,来源/授权只 warning 不阻断;正式交付再替换为授权清晰的 file token / 本地资产 | `slides +media-upload`,或 `+create --slides` / `+create-svg``@./path` 占位符 |
| 在 slide 中绘制柱/条/折线/面积/雷达/饼等有数据序列的图表 | 使用原生 `<chart>` 元素 | `xml-schema-quick-ref.md` |
| 在 slide 中绘制流程图、时序图、架构图、散点图、漏斗图或装饰图案 | 必须先用 Read 工具读取参考文档,再生成 `<whiteboard>` 元素 | [`lark-slides-whiteboard.md`](references/lark-slides-whiteboard.md) |
| 使用语义图标 | 先检索 IconPark再写 `<icon iconType="...">` | `iconpark_tool.py search → resolve``iconpark.md` |
@@ -32,7 +32,11 @@ metadata:
**CRITICAL — 走 `slides +create-svg` 时,输入必须是 SVGlide SVGroot `<svg>` 声明 `xmlns:slide` 且 `slide:role="slide"`;可渲染 SVG 元素必须用 `slide:role="shape"` 或 `slide:role="image"` 表达;`g` / 嵌套 `svg` 可作为容器,但容器内实际渲染元素仍必须各自声明 role。CLI 只读取文件、上传/替换图片占位符、注入 transport metadata 和调用现有 `/slide` 路由,不会把普通 SVG 自动补齐成协议 SVG。**
**CRITICAL — 高质量 SVG deck 生成时MUST 同时读取 [lark-slides-create-svg.md](references/lark-slides-create-svg.md):复用现有 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json` 作为设计状态,先做 deck-level density plan再定义布局盒给 `foreignObject` 文本留足安全高度,默认必须使用真实图片资产(本地 `@./path` 或 file token相邻页面要显著换版式调用 API 前必须跑本地 preflight优先使用 [`scripts/svg_preflight.py`](scripts/svg_preflight.py)live 创建后必须 readback 校验。这些是生成技巧,不替代 [svg-protocol.md](references/svg-protocol.md) 的硬协议约束**
**CRITICAL — SVGlide deck 页数默认值:当用户要求生成 SVG/SVGlide 幻灯片但未说明页数,或使用“一份 slide / 一份 PPT / 做个 slide / 生成一个 slide”这类模糊表达时默认生成 `10` 页,不要仅因页数缺失而停下来追问。只有用户明确说“一页 / 单页 / onepage / one slide / 只要封面”等单页意图时,才生成 `1` 页;用户给出明确页数时始终服从用户要求。默认 10 页时必须在 `slide_plan.json` 写入 `page_count` 或 `target_slide_count=10`,并包含明确 closing slide**
**CRITICAL — 高质量 SVG deck 生成时MUST 同时读取 [lark-slides-create-svg.md](references/lark-slides-create-svg.md) 和 [svg-visual-recipes.md](references/svg-visual-recipes.md):复用现有 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json` 作为设计状态,先做 deck-level density plan再为每页选择 `visual_recipe`、声明 `svg_primitives` / `visual_focal_point` / `xml_like_risk`,然后定义布局盒,给 `foreignObject` 文本留足安全高度。生成器必须在写 SVG 前做 preflight-aware 自检:由实际组件 manifest 反推出 primitives按 `content_density_contract` 计数,检查主体元素 safe area / text bbox不要只靠最终 `svg_preflight.py` 兜底。Preview 阶段默认必须使用丰富真实图片资产,并 SHOULD 优先根据用户 query / deck 主题 / 章节标题去网络检索和拉取强相关图片;公开图、场景图、产品图、截图、纹理/材质、图鉴图均可作为占位视觉。版权/授权不作为 preview 阻断,但要在 `asset_contract` 里标记 `retrieval_query`、`source_url` 和 `preview_unverified`;正式交付再替换为授权清晰的本地 `@./path` / file token。相邻页面要显著换版式且 8 页以上至少使用 5 种 visual recipe family如果 agent 支持本地浏览器预览SHOULD 生成并打开 `preview.html`,并按 [svg-aesthetic-review.md](references/svg-aesthetic-review.md) 检查明显视觉问题;调用 API 前必须跑本地 preflight优先使用 [`scripts/svg_preflight.py`](scripts/svg_preflight.py)live 创建后必须 readback 校验。这些是生成技巧,不替代 [svg-protocol.md](references/svg-protocol.md) 的硬协议约束。**
**CRITICAL — SVGlide 高质量生成必须读取 [style-presets.md](references/style-presets.md),并从 [style-presets.json](references/style-presets.json) 选择一个 deck-level `style_preset`。`style_preset` 只表达视觉语言,不替代 `visual_recipe``visual_recipe` 的选择和安全效果边界以 [svg-visual-recipes.md](references/svg-visual-recipes.md) 为准。生成顺序是 semantic plan -> visual_recipe -> style_preset/style_system -> layout boxes -> SVG。每页必须声明 `visual_signature` 和 `svg_effects`,说明这一页相对普通 XML/PPT 模板的 SVG 视觉优势。**
**CRITICAL — 新建演示文稿或大幅改写页面时MUST 先生成 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json`,再生成 XML 或 SVGlide SVG。先创建对应目录规划层规则和中间产物生命周期见 [planning-layer.md](references/planning-layer.md)。仅替换一个标题、插入一个块等小型已有页编辑可豁免。**
@@ -40,7 +44,7 @@ metadata:
**CRITICAL — 新建演示文稿或大幅改写页面时,规划 `asset_need` MUST 遵循 [asset-planning.md](references/asset-planning.md):只做元数据规划,必须有 `fallback_if_missing`,不得要求真实搜索、下载或上传素材。**
**CRITICAL — 创建或大幅改写后MUST 按 [validation-checklist.md](references/validation-checklist.md) 做显式验证:回读全文 XML、核对页数和关键元素、检查空白/破损页、明显溢出、布局风险XML 语法和文本重叠静态检查优先使用 [`scripts/xml_text_overlap_lint.py`](scripts/xml_text_overlap_lint.py)SVG 创建前的本地 preflight 优先使用 [`scripts/svg_preflight.py`](scripts/svg_preflight.py)。**
**CRITICAL — 创建或大幅改写后MUST 按 [validation-checklist.md](references/validation-checklist.md) 做显式验证:回读全文 XML、核对页数和关键元素、检查空白/破损页、明显溢出、布局风险XML 语法和文本重叠静态检查优先使用 [`scripts/xml_text_overlap_lint.py`](scripts/xml_text_overlap_lint.py)SVG 创建前的本地 preflight 优先使用 [`scripts/svg_preflight.py`](scripts/svg_preflight.py)SVG 本地预览后按 [svg-aesthetic-review.md](references/svg-aesthetic-review.md) 做审美和重复问题复核**
**CRITICAL — 创建前自检或失败排障时MUST 按 [troubleshooting.md](references/troubleshooting.md) 检查 XML 转义、结构、shell 截断、图片 token、3350001 和布局风险。**
@@ -85,7 +89,7 @@ lark-cli auth login --domain slides
按需再读:
- 创建:[`lark-slides-create.md`](references/lark-slides-create.md)SVG 创建:[`lark-slides-create-svg.md`](references/lark-slides-create-svg.md)、[`svg-protocol.md`](references/svg-protocol.md)
- 创建:[`lark-slides-create.md`](references/lark-slides-create.md)SVG 创建:[`lark-slides-create-svg.md`](references/lark-slides-create-svg.md)、[`svg-protocol.md`](references/svg-protocol.md)、[`style-presets.md`](references/style-presets.md)、[`svg-visual-recipes.md`](references/svg-visual-recipes.md)、[`svg-aesthetic-review.md`](references/svg-aesthetic-review.md)
- 编辑:[`lark-slides-edit-workflows.md`](references/lark-slides-edit-workflows.md)、[`lark-slides-replace-slide.md`](references/lark-slides-replace-slide.md)
- 图片:[`lark-slides-media-upload.md`](references/lark-slides-media-upload.md)
- 流程图 / 时序图 / 架构图 / 装饰图案:[`lark-slides-whiteboard.md`](references/lark-slides-whiteboard.md)
@@ -133,8 +137,9 @@ lark-cli auth login --domain slides
- 不要所有页面复用同一种标题 + 三 bullets 版式。
- 不要用低对比文字或低对比图标,例如浅灰字压在浅色背景上。
- 不要让装饰线穿过文字,或让页脚、来源、编号挤压主体内容。
- 不要使用版权状态不明的图片、logo、截图或素材图片必须来自用户提供、公司/项目自有、明确可商用授权图库,或授权条件清晰的 AI 生成资产,并在产物说明或素材清单中记录来源、授权/许可类型、原始 URL 和是否需要署名
- Preview 阶段不要因为版权/授权缺失而退回纯矢量;推荐先把用户 query、deck 标题和每页章节主题拆成图片检索词,去网络拉取强相关真实图片、网页截图、产品截图或图库图做视觉占位。必须记录 `retrieval_query`、来源 URL或标记 `license=preview_unverified`,并避免误导性商标背书、敏感肖像和明显不适当素材。正式交付时再替换为用户提供、公司/项目自有、明确可商用授权图库,或授权条件清晰的 AI 生成资产
- 不要把素材缺失表现为空白图片框;必须先尝试获取或生成可用图片资产。只有用户明确要求纯矢量、网络/权限不可用,或主题确实不适合图片时,才按 `fallback_if_missing` 生成 XML-native 视觉,并在结果中说明。
- Preview/MVP 阶段图片来源/授权/外链问题不作为 `svg_preflight.py` 的 hard blocker但必须保留 warning 并在 live readback 后检查图片是否可见;正式交付仍优先用本地 `@./path` 自动上传或 file token。
- 不要留下模板占位文案、示例公司名、示例日期或与用户主题无关的原模板内容。
### 创建方式选择
@@ -164,7 +169,7 @@ python3 skills/lark-slides/scripts/template_tool.py extract --template <template
```text
Step 1: 需求澄清 & 读取知识
- 澄清主题、受众、页数、风格;模板需求按“模板与脚本优先流程”处理
- 澄清主题、受众、页数、风格;SVGlide 模糊页数按默认 10 页处理,不因页数缺失单独阻塞;模板需求按“模板与脚本优先流程”处理
- 读取 xml-schema-quick-ref.md新建 / 大幅改写时还要读取 planning-layer.md、visual-planning.md、asset-planning.md
Step 2: 生成大纲 → 用户确认 → 写入 slide_plan.json
@@ -293,7 +298,7 @@ lark-cli slides <resource> <method> [flags] # 调用 API
5. **保存关键 ID**:后续操作需要 `xml_presentation_id``slide_id``revision_id`
6. **删除谨慎**:删除操作不可逆,且至少保留一页幻灯片
7. **编辑已有页面优先块级替换**:修改单个 shape/img 用 `+replace-slide``block_replace` / `block_insert`),不要整页重建;只有需要替换整页结构时才用 `slide.delete` + `slide.create`
8. **图片只能用上传到飞书 drive 的 `file_token`,禁止使用 http(s) 外链 URL**XML 路径使用 `<img src="...">`SVG 路径使用 `<image slide:role="image" href="...">`流程必须是「先把图存到本地 → 用 `slides +media-upload` 上传,或 `+create --slides` / `+create-svg``@./path` 占位符自动上传 → 拿 `file_token` 写进图片引用」。如果用户给了网图链接,先 `curl`/下载到 CWD 内再走上传流程,不要直接把外链 URL 塞进 `src` / `href`。**图片最大 20 MB**slides upload API 不支持分片上传)。
8. **Preview 阶段图片要优先丰富,不要纯矢量兜底**XML 路径使用 `<img src="...">`SVG 路径使用 `<image slide:role="image" href="...">`推荐流程是「从用户 query / 页面主题生成图片检索词 → 网络拉取主题强相关图片 → 存成本地资产 → 用 `slides +media-upload` 上传,或 `+create --slides` / `+create-svg``@./path` 占位符自动上传 → 拿 `file_token` 写进图片引用」。Preview/MVP 阶段 `svg_preflight.py` 对 http(s) / data 图片、来源/授权不完整只 warning不阻断如果时间紧可先保留公开可访问图片 URL 做视觉验证,并在 `asset_contract` 标记 `retrieval_query``source_url` `preview_unverified`。正式交付再统一替换为本地 `@./path` 或 file token。**图片最大 20 MB**slides upload API 不支持分片上传)。
## 权限速查

View File

@@ -96,6 +96,18 @@ SVG 创建不使用单独的规划目录。新建或大幅改写 SVG deck 时,
"output_mode": "svglide-svg",
"canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"},
"safe_area": {"x": 48, "y": 40, "width": 864, "height": 460},
"style_preset": "raw_grid",
"style_selection_reason": "raw_grid fits technical training pages that need dense but readable visual structure",
"style_system": {
"palette": {
"background": "#F5F5F5",
"text": "#0A0A0A",
"accent": "#F2D4CF"
},
"typography": "strong title, readable native text labels",
"background_strategy": "muted grid panels with one stable background family",
"motif": "dense grid panels with restrained accent labels"
},
"svg_constraints": {
"text_element": "foreignObject slide:role=shape slide:shape-type=text",
"path_commands": "M/L/H/V/C/Q/Z only",
@@ -106,7 +118,7 @@ SVG 创建不使用单独的规划目录。新建或大幅改写 SVG deck 时,
{"page": 1, "path": ".lark-slides/plan/<deck-id>/pages/page-001.svg"}
],
"preflight": {
"command": "python3 skills/lark-slides/scripts/svg_preflight.py --input .lark-slides/plan/<deck-id>/pages/page-001.svg",
"command": "python3 skills/lark-slides/scripts/svg_preflight.py --plan .lark-slides/plan/<deck-id>/slide_plan.json --input .lark-slides/plan/<deck-id>/pages/page-001.svg",
"status": "pending"
},
"readback_verification": {
@@ -118,6 +130,138 @@ SVG 创建不使用单独的规划目录。新建或大幅改写 SVG deck 时,
模板也复用现有 `template_tool.py search -> summarize -> extract` 路由。模板摘要只用于选择主题、页面流、视觉节奏和布局骨架;生成 SVG 时要把模板结构翻译成 SVG layout boxes / visual recipes不要照搬模板 XML也不要读取完整模板 XML。
SVG deck 的 `slides[]` 还必须包含这些可校验字段,避免生成结果虽然能创建但内容千篇一律、信息量不足或在资料缺失时编造事实:
```json
{
"page": 3,
"page_type": "content",
"renderer_id": "dashboard_scorecard",
"layout_family": "dashboard",
"visual_recipe": "fake_ui_dashboard",
"visual_intent": "use a product-console dashboard surface to make metrics feel operational",
"visual_focal_point": "central metric card and trend line",
"visual_signature": "fake product console frame + micro chart geometry + status chips",
"svg_effects": ["chart_geometry", "connector_flow", "typography"],
"required_primitives": ["dashboard", "micro_chart"],
"svg_primitives": ["dashboard", "micro_chart", "typography", "geometric_shape"],
"xml_like_risk": "without SVG primitives this page would degrade into three metric cards plus bullets",
"recipe_fallback": "if dashboard micro charts are too dense, keep the fake UI frame and simplify charts to bar-like rects",
"density": "high",
"density_structure": "dashboard with four metric cards, trend line, and source note",
"content_density_contract": "dashboard >= 4 metrics",
"asset_contract": "none_required | {mode: preview|production, retrieval_query, source_type, license, local_path_or_href, usage_page, source_url/generated_by, replacement_required}",
"risk_flags": ["text_overflow", "image_license", "conversion_dasharray"],
"source_status": "source_verified | attachment_missing | user_prompt_only",
"source_policy": "when attachment_missing, show 待从附件补齐 / 来源缺失 and avoid numeric claims",
"layout_guardrails": [
"renderer_id must change actual geometry, not only the name",
"visual_recipe must map to SVGlide-safe primitives present in the SVG source",
"main text and chart labels stay inside safe area",
"dense page uses a structured visual carrier, not a long bullet box",
"avoid XML-like card layout unless the page has real SVG-native visual structure"
]
}
```
### Style Preset Catalog
SVGlide 高质量生成必须先从 [style-presets.json](style-presets.json) 选择一个 deck-level `style_preset`,并把它翻译成 `style_system``style_preset` 不替代 `visual_recipe`:前者定义视觉语言,后者定义页面结构和 SVG-native 表达价值。
生成前还必须读取 [svg-visual-recipes.md](svg-visual-recipes.md)。该文件是当前 CLI 执行链路的短规则入口,负责把研究 catalog 映射成可写入 `slide_plan.json` 的 underscore `visual_recipe` 枚举、安全效果边界和 deck 多样性要求。
生成顺序:
```text
semantic plan
-> visual_recipe
-> style_preset + style_system
-> layout boxes
-> SVG source
-> svg_preflight.py --plan
```
`style_system` 至少包含:
- `palette`: 背景、正文、强调色。
- `typography`: 标题、标签、正文的字号/字重策略。
- `background_strategy`: 全 deck 背景和例外页规则。
- `motif`: 可复用的视觉母题,例如 grid panels、stamp labels、court lanes、riso color plates。
每页必须声明:
- `visual_signature`: 这一页相对普通 XML/PPT 模板的独特 SVG 视觉记忆点。
- `svg_effects`: 真实使用或计划使用的 SVG 表达能力,例如 `path``connector_flow``gradient``texture``chart_geometry``image_overlay`
`svg_preflight.py` 会校验 preset 是否存在、`style_system` 是否完整、可见文本是否泄漏 preset 名称/source token/tool/path以及 declared `svg_effects` 是否能在 SVG source 中命中。
### SVG-native visual recipe catalog
SVG 不是普通矢量图文件的传输外壳。每页都必须选择一个 `visual_recipe`,并在 `svg_primitives` 中声明真实会绘制的 SVGlide-safe primitives。`renderer_id` 负责几何布局命名;`visual_recipe` 负责说明这页为什么值得走 SVG。
本节保留协议内置摘要;实际生成前优先读 [svg-visual-recipes.md](svg-visual-recipes.md),避免把研究文档里的 dotted recipe 名称直接写进运行时 plan。
| `visual_recipe` | 适用页型 | required primitives | forbidden patterns / fallback |
|---|---|---|---|
| `hero_typography` | 封面、章节页、观点页 | `typography`, `geometric_shape` | 不要只写普通标题;大字用 `foreignObject`,描边/裁切感用大字背板、路径轮廓或分层 shape 模拟 |
| `geometric_composition` | 战略框架、阶段划分、版式强分区 | `geometric_shape`, `path` | 不要只堆 3 个矩形卡片;斜切块、多边形全部用 `path` 写,不用 `polygon` |
| `path_flow` | 路线、旅程、流程、增长路径 | `path`, `annotation` | 不依赖 `marker` / `stroke-dasharray`;箭头用显式三角 `path`,虚线用短 line/dot 组合 |
| `infographic_scorecard` | 数据战报、OKR、业务复盘 | `typography`, `micro_chart` | 不要只放大数字;补环形/条形/标尺等微图表,圆环用双层填充圆或 path |
| `icon_capability_map` | 能力地图、模块总览、平台能力 | `icon`, `geometric_shape` | 图标用 SVGlide-safe path/line/rect 组合,不用外链 iconfont 或根级 `<text>` |
| `gradient_depth` | 能力升级、概念页、氛围页 | `gradient`, `geometric_shape` | 渐变只作为层次,不能替代信息结构;关键文字必须有深色承载底 |
| `mask_clip_showcase` | 成果展示、产品/品牌视觉页 | `typography`, `image_overlay` | 不直接依赖 `mask` / `clipPath`;用大字描边、半透明 shape 遮罩、裁切安全区模拟 |
| `technical_texture` | 技术架构封面、工程系统页 | `texture`, `path` | 不用 `<pattern>`;网格、点阵、扫描线用重复 line/circle/rect 显式绘制 |
| `metaphor_loop` | 闭环、反馈系统、运营机制 | `path`, `geometric_shape` | 不只画 4 个圆节点旁边必须补机制表、KPI 标签、输入输出或责任说明 |
| `spotlight_annotation` | 问题定位、架构标注、案例诊断 | `spotlight`, `annotation` | 发光用多层半透明 circle/rect/path 模拟,不依赖复杂 filter标注线和 callout 必须对齐目标 |
| `fake_ui_dashboard` | 产品能力、CLI/平台/监控展示 | `dashboard`, `micro_chart` | 不要把 3 张指标卡伪装成 dashboard必须有 UI frame、状态栏、图表/日志/趋势等操作界面细节 |
| `brand_system` | 系列化 deck、主题页、收尾页 | `typography`, `geometric_shape` | 不只换颜色;必须复用标题位置、边栏、编号、强调色、图标线宽或背景 motif |
`svg_preflight.py` 会校验 `visual_recipe` 枚举、必填字段、recipe required primitives、8 页以上 recipe family 多样性,以及 plan 声明的 primitives 是否能在 SVG source 中检测到。生成器不能只在 plan 里声明 recipe实际仍画 XML 式卡片。
### 生成阶段 Fail-Fast Gate
`slide_plan.json` 不是说明文档,而是生成阶段的硬契约。生成器必须先通过 plan gate再渲染 SVG本地 `svg_preflight.py --plan` 失败时禁止调用 live API。
每页 SVG plan 必填:
| Field | 作用 | 失败后处理 |
|---|---|---|
| `renderer_id` | 标识具体渲染器/几何结构 | 换真实 renderer不用 `two_column_1` 这类假命名 |
| `layout_family` | 做 deck 级版式多样性检查 | 相邻页重复时换阅读方向、主视觉位置或信息结构 |
| `visual_recipe` | 说明这页为什么值得走 SVG | 从 recipe catalog 选择,不能自造枚举 |
| `required_primitives` | 这页必须在 SVG source 中真实出现的 primitive | 至少覆盖 recipe required primitives |
| `svg_primitives` | 实际计划绘制的 primitive | 必须覆盖 `required_primitives` |
| `visual_intent` | SVG 视觉表达目的 | 写清楚 SVG-native 价值,不写空泛风格词 |
| `visual_focal_point` | 页面视觉焦点 | 用于判断布局是否围绕主视觉组织 |
| `xml_like_risk` | 退化成普通 XML 卡片页的风险 | 明确说明不用 SVG 会丢失什么结构 |
| `content_density_contract` | 信息密度硬契约 | 高密度页必须量化,例如 `dashboard >= 4 metrics` |
| `asset_contract` | 图片/素材来源与许可契约 | 无图写 `none_required`Preview 网络图必须记录 `retrieval_query` / `source_url`,授权未确认可写 `license=preview_unverified` 且不阻断;正式交付必须补 source/license/local path 或替换 |
| `risk_flags` | 生成风险显式登记 | 无风险用空数组;不要省略字段 |
| `source_policy` | 缺数据/数字声明处理策略 | 防止自动扩写时编造业务数字 |
deck 级硬门禁:
- 用户未说明页数,或只说“一份 slide / 一份 PPT / 做个 slide / 生成一个 slide”这类模糊表达时默认 `page_count=10`;不要仅因页数缺失而停下来追问。明确“一页 / 单页 / onepage / one slide / 只要封面”才按 `page_count=1`。默认 10 页必须包含 closing slide并满足 10 页 deck 的 layout / renderer 多样性门禁。
- 8 页以上必须有明确 closing slide。
- 10 页以上至少 5 种 `layout_family`
- 不允许连续 3 页使用同一 `layout_family`
- 8 页以上至少 5 种 `visual_recipe` family。
- 10 页以上至少 5 种真实 `renderer_id`
- 高密度页必须有量化 `content_density_contract`,不能只写“信息丰富”。
量化密度契约建议:
```text
matrix/table >= 6 cells
timeline >= 4 nodes
dashboard >= 4 metrics
flow >= 4 stages
risk_grid >= 4 items
comparison >= 4 rows or columns
```
如果 SVG source 无法满足对应数量,`svg_preflight.py` 会报 `plan_content_density_contract_not_met`,生成器必须补真实结构,不要只改字段名。
### 生成前强约束
以下规则来自实际 SVGlide live 生成、回读和修复经验,生成器必须先满足这些规则,再追求视觉复杂度。
@@ -125,7 +269,15 @@ SVG 创建不使用单独的规划目录。新建或大幅改写 SVG deck 时,
- MUST: 默认使用 Lark Slides 当前回读画布 `960 x 540`,即 root 写成 `width="960" height="540" viewBox="0 0 960 540"`。不要默认用 `1280 x 720`,否则服务端回读后可能整页偏大并裁切。
- MUST: 主体元素使用安全区,建议 `safe = x:48 y:40 w:864 h:460`。除全屏背景外,文本、卡片、图表、标签、节点和图例都必须落在安全区内。
- MUST: 多页 deck 应包含明确的 closing slide。8 页以上讲解/汇报型 deck 不要把 roadmap / next-playbook 当作结束页;最后一页应包含 `closing``summary``Q&A``Thanks` 或下一步联系信息。
- MUST: `slides[]` 必须记录 `renderer_id`,且它要对应真实几何结构,而不是 `two-column-1` / `two-column-2` 这种名字变化。10 页以上 deck 至少 5 种 renderer/layout family不得连续 3 页使用同一 renderer。
- MUST: `slides[]` 必须记录 `layout_family``visual_recipe``visual_intent``visual_focal_point``required_primitives``svg_primitives``xml_like_risk``content_density_contract``risk_flags``source_policy``asset_contract` 应尽量记录MVP 阶段缺失只 warning。没有 SVG-native recipe 的页面不应走 `slides +create-svg`,应改用普通 Slides XML 或重新选择 SVG recipe。
- MUST: `visual_recipe` 必须来自 catalog`svg_primitives` 必须覆盖该 recipe 的 required primitives。`renderer_id` 不能替代 `visual_recipe`
- MUST: 8 页以上 SVG deck 至少使用 5 种 visual recipe family不能整套 deck 都是卡片、双栏或普通 dashboard。
- MUST: 高密度页必须声明 `density_structure` 和量化 `content_density_contract`,例如 `matrix/table >= 6 cells``timeline >= 4 nodes``dashboard >= 4 metrics``flow >= 4 stages``risk_grid >= 4 items`。只有“大标题 + 大图 + 2-3 个短 chip”不算高密度。
- MUST: 来源不足、附件缺失、用户未提供数据时,必须在 plan 中写 `source_status``source_policy`,并在页面上显式表达“待从附件补齐 / 来源缺失 / no numeric claims”。不要编造客户、排名、真实论文数据、金额、占比、链接、logo 或引用。
- MUST: `foreignObject` 文本样式使用显式 CSS`font-size``font-weight``font-family``color``line-height``text-align`。不要用 `font:` shorthand 表达关键字号和加粗。
- MUST: 白色或接近白色的文字必须完整落在深色 shape 承载底上。标题、封面副标题、CTA、页脚等不能跨出深色底压到浅色图片、白色蒙层或白底上需要时扩大色块、加深色背板/遮罩,或改用深色文字。
- MUST: 圆形/椭圆节点只承载短标签,不承载解释句。节点内 `foreignObject` bbox 必须小于节点 bbox微解释、指标、下一步和注释放到独立说明卡、图例、机制表或外侧 callout。
- MUST: 提交前和 live 回读后都检查边界和重叠:非背景元素不得越过 `960 x 540`,第 2/3 页等信息密集页必须额外检查 text bbox overlap。
- SHOULD: 如果本地预览使用更大画布,例如 `1280 x 720`,必须在输出给 `slides +create-svg` 前按比例换算为 `960 x 540`,而不是只改 root viewBox。
@@ -138,25 +290,69 @@ SVG 创建不使用单独的规划目录。新建或大幅改写 SVG deck 时,
- MUST: SVG 生成 helper 的返回类型保持一致。推荐统一返回 `string`,或统一返回 `string[]` 后在页面末尾 `flat().filter(Boolean).join("\n")`;不要混用 `...items.map(...).join("\n")`,这会把已拼好的 SVG 标签按字符展开,生成非法 XML。
- MUST: 所有组件都从稳定布局盒推导坐标,避免散点手调。文本、标签、图例、曲线端点和卡片内容应有明确的父盒和对齐规则。
- MUST: 生成脚本要先写 deck plan / asset list再写页面不能边补坐标边生成最终 SVG。
- MUST: 生成器要把 preflight 规则前移为本地 assert。写 SVG 前先由实际组件 manifest 反推出 `svg_primitives`,再检查 `visual_recipe` required primitives、`required_primitives``content_density_contract` 数量、主体 safe area、文本 bbox 和最小文本框高度;断言失败时修组件或布局,不要只改 `slide_plan.json` 字段。
- MUST: 高密度结构要由组件实际数量驱动,例如 `scorecard >= 4 metrics` 必须生成 4 个能被识别为 metric/bar/card 的元素;`timeline >= 4 nodes` 必须生成 4 个真实节点和标签;不要用文字描述冒充结构。
- MUST: 文本组件要按字号、行高和预估行数计算最小 `foreignObject` 高度。卡片、节点、脚注、图例的正文框不得出现 0、高度个位数或明显低于一行文字的 bbox。
- MUST: 主体文本、卡片、图表、标签、节点和图例必须落在 safe area全画布背景、边缘承载底、图片遮罩和装饰边框可以超出 safe area但应只承担背景/承载作用,不承载关键文本。
- SHOULD: 对高风险页面使用更保守的留白:标题与图表标签至少相隔 24px曲线端点标签不要压在标题/图例区域,卡片内文字与边框至少留 10-14px。
- SHOULD: 把每页的 `safe``titleBox``visualBox``textBox` 等布局盒保存为可检查数据,便于自动计算越界和重叠。
推荐生成顺序:
```text
deck/page plan
-> layout boxes
-> components with emitted primitive manifest
-> generator asserts: recipe/primitives/density/text/safe-area
-> write SVG + slide_plan.json from the same manifest
-> svg_preflight.py --plan ...
-> dry-run / live create / readback
```
### 本地 HTML 预览(建议)
HTML 预览是生成阶段的轻量质检,不是 SVGlide 协议或 CLI API 的硬依赖。
- SHOULD: 生成 SVGlide deck 后、调用 `slides +create-svg` 前,生成一个本地 `preview.html`,把每页 SVG 按 16:9 画布嵌入,并展示页码、标题、`renderer_id` / `visual_recipe`、图片资产状态、preview-only 图片来源和明显 warning。
- SHOULD: 如果当前 agent、IDE 或浏览器工具支持打开本地文件,打开 `preview.html` 进行人工或截图式预览,优先检查:
- 页面是否空白、明显裁切或整体偏大。
- 标题、正文、图片和装饰元素是否重叠。
- 白色/浅色文字是否压到浅色背景或图片亮部。
- 相邻页面是否版式过度重复。
- 信息密度是否明显不足,尤其是高密度页是否真的有 matrix/table/timeline/dashboard/flow/risk grid。
- 结尾页是否存在。
- 图片是否显示,是否有破图、空图片框、图片过少或 preview-only 来源未记录。
- SHOULD: 在最终产物目录记录 `preview.html` 路径;如果未生成或无法打开,说明原因,并继续执行 preflight / dry-run / readback。
- MUST NOT: 用 HTML 预览替代 `svg_preflight.py``slides +create-svg --dry-run` 或 live readback。HTML 预览主要提前发现审美、布局和素材问题服务端转换后的字体、path bbox、图片 token 和部分 SVG 效果仍必须通过 readback 验证。
打开预览后必须按 [svg-aesthetic-review.md](svg-aesthetic-review.md) 做一次人工或截图式审查。重点看所有页面的标题区、装饰线、badge、文本框、图片框、safe area、重复版式和 SVG 视觉优势;如果多页出现同类问题,修生成规则后重新生成,不要只逐页微调坐标。
本地 preflight 必须在 `slides +create-svg` 前执行,失败即停:
- `python3 skills/lark-slides/scripts/svg_preflight.py --input page-*.svg` 通过;如果脚本不可用,再退回 `xmllint --noout page-*.svg` 加人工检查。
- `python3 skills/lark-slides/scripts/svg_preflight.py --plan .lark-slides/plan/<deck-id>/slide_plan.json --input page-*.svg` 通过;如果脚本不可用,再退回 `xmllint --noout page-*.svg` 加人工检查。
- root 是 `width="960" height="540" viewBox="0 0 960 540"`
- root / leaf `slide:role` 完整,所有 leaf 有几何必填属性。
- 禁止 `font:` shorthand、http(s) / data URL 图片、未下载的远程图片、空图片框
- plan 中每页 `layout_family``visual_recipe``visual_intent``visual_focal_point``required_primitives``svg_primitives``xml_like_risk``content_density_contract``risk_flags``source_policy` 完整,且 recipe required primitives 能在对应 SVG source 中命中。`asset_contract` 在 MVP 阶段缺失只 warning有条件时仍应补全
- 禁止 SVG 退化成 XML-like 卡片页:如果页面基本只有 `rect + foreignObject`,且没有 path、gradient、image overlay、annotation、micro chart、icon、texture、spotlight、flow 等 SVG-native primitivepreflight 必须失败。
- 禁止零尺寸元素;文本框、图片、卡片和圆/椭圆必须有正向宽高,不能生成 `height="0"` 的隐藏说明。
- `<image opacity="...">` 或图片 style 里写 `opacity:` 在 MVP 阶段只 warning当前转换链路不会稳定保留到 readback `<img>`。需要淡化图片时,优先把透明度预合成进 PNG/JPG或在图片上方加半透明 `rect` 遮罩。
- 禁止白色/浅色文字跨出深色承载底;如果 preflight 报 `light_text_without_dark_backing`,优先扩大深色背景或加文本背板,不要只缩小字号。
- 禁止把解释文字塞进圆形/椭圆节点;如果 preflight 报 `node_text_overflow`,节点内只保留短标签,把说明迁移到旁边卡片、表格或图例。
- 警惕 `circle` / `ellipse``stroke-width`;当前转换链路可能只保留 border color 而丢失 width。关键圆环、节点外圈和粗描边用双层填充圆/椭圆模拟,或改成 path/rect。
- 禁止关键路线、闭环、流程连接、timeline rail 使用 `stroke-dasharray`;普通装饰虚线也会 warning。关键路线必须用显式短线段或小圆点 markers 组成,不要把虚线作为唯一视觉表达。
- 禁止 `font:` shorthand 和空图片框。MVP 阶段 http(s) / data URL 图片、未下载远程图片只 warning正式交付和可见性要求高的 deck 仍应下载到本地并走 `@./path` 上传或使用 file token。
- 禁止 unsupported path command`path d` 只含 `M/L/H/V/C/Q/Z`
- 非背景元素不得越界;主体元素应在 safe area 内。
- 文本框做 bbox overlap 近似检查,尤其是目录、痛点、竞品表、案例图表和总结页。
- 图片资产文件存在、大小合理、授权来源清单完整
- 图片资产文件存在、大小合理,或 http(s)/data URL 能在 preview 中显示。Preview 阶段来源/授权不完整只 warning但必须用 `asset_contract.license=preview_unverified``risk_flags=["image_preview_only"]` 显式标记;正式交付再补齐来源/授权或替换
- deck plan 通过 renderer 多样性、layout family 多样性、closing slide、高密度结构、资产契约、来源保护六类校验。
创建顺序:
```text
generate deck plan -> generate assets -> generate SVG files
-> local preflight -> lark-cli slides +create-svg --dry-run
-> optional preview.html and browser preview when supported
-> local preflight with --plan -> lark-cli slides +create-svg --dry-run
-> live create -> xml_presentations get readback
-> readback bbox / text overlap / closing slide checks
```
@@ -175,7 +371,7 @@ readback 不能省略。服务端会把 SVGlide 转成 Slides XML文字 bbox
"audience": "who will read it",
"goal": "what the deck should make the audience understand or decide",
"density_strategy": "how low/medium/high density pages are distributed",
"asset_strategy": "which real images are needed, where they will be used, copyright/license source, and fallback if unavailable",
"asset_strategy": "which query/topic-related web images should be searched and fetched, where they will be used, preview source/url/license risk, and production replacement plan if needed",
"visual_rhythm": "how layout, imagery, charts, and text density vary across pages",
"slides": [
{
@@ -204,8 +400,10 @@ risk, tradeoff, summary, closing, q-and-a, appendix
密度规则:
- MUST: 每页都要有明确 `takeaway`,即使是封面、分隔页和结束页。
- MUST: 每个 SVG deck 默认都要包含真实图片资产,不要全程只用矢量 shape 冒充“配图”。展示型、宣传型、产品型、品牌型和案例型 deck 至少包含 3 处图片使用,其中至少 1 页使用全幅或半出血图片主视觉。
- MUST: 每个 SVG deck 默认都要包含真实图片资产,不要全程只用矢量 shape 冒充“配图”。Preview 阶段应优先根据用户 query、deck 标题和页面主题去网络检索并拉取强相关图片,再补充产品截图、网页截图、场景图、材质纹理、图鉴图和 AI 生成图增强视觉冲击;展示型、宣传型、产品型、品牌型和案例型 deck 至少包含 3 处图片使用,其中至少 1 页使用全幅或半出血图片主视觉。
- MUST: 高密度页必须有承载信息的视觉结构,例如矩阵、流程、地图、时间线、标注图、案例卡或手绘微图表,不能只有装饰图形。
- MUST: 生成器必须先扩写页面“结构信息”,再绘制 SVG。信息密度不足时优先补结构化解释层例如编号标签、微解释、比较维度、轴线、图例、阶段、来源状态、下一步而不是把同一句话换写成多个 chip。
- MUST: 流程页、闭环页、机制页和产品体系页不能只有“4 个圆节点 + 短标签”。至少补 1 层结构化信息例如机制表、KPI 标签、触发条件、责任/频率、输入输出、风险提示或下一步动作。
- SHOULD: 高密度内容页通常包含 3-6 个信息块和若干可读细节,但 executive brief、品牌页、产品视觉页、短汇报可以降低数量只保留强结论、关键证据和视觉锚点。
- SHOULD NOT: 不要让所有高密度页长成同一种“主结论 + 3-6 卡片 + 3 个 callout”模板。
- MUST NOT: 缺少素材或数据时不要编造数字、客户名、logo、排名、引用或真实案例用 qualitative label、relative scale、hypothesis/assumption 标注兜底。
@@ -263,7 +461,7 @@ notesGrid = x:54 y:430 w:760 h:48
### 文本安全余量
`foreignObject` 文本优先使用显式 CSS。为了服务端转换到 SXSD/XML 后保留样式,字号、加粗、颜色、行距和对齐必须写成独立属性;不要把关键样式藏在 `font:` shorthand 或只写在复杂外层 wrapper 上:
`foreignObject` 文本优先使用显式 CSS。为了服务端转换后保留样式字号、加粗、颜色、行距和对齐必须写成独立属性不要把关键样式藏在 `font:` shorthand 或只写在复杂外层 wrapper 上:
```xml
<foreignObject slide:role="shape" slide:shape-type="text" x="54" y="62" width="600" height="42">
@@ -281,6 +479,8 @@ notesGrid = x:54 y:430 w:760 h:48
- 小型标签文本盒不小于 14px。
- 多行文字要按行高预估高度,再额外留 8-12px。
- 右侧图例或矩阵格里的文字不得贴边,水平 padding 至少 10-14px。
- 白色/浅色文字的 bbox 必须完全落在深色 rect/card/overlay 内;封面标题如果跨出色块,应优先扩大色块或改成深色字,不要让白字压在浅色图片或白色蒙层上。
- 圆形/椭圆节点内只放短标签,解释文字移动到节点外的 callout、legend 或机制表;不要让圆内文本框宽度超过圆形直径。
- 服务端支持 `foreignObject` 内的 `<br />`。为了本地预览和标题排版稳定,标题/大段文本优先使用多个块级 `div``p` 控制行高,不要只靠 `<br />` 调整复杂布局。
- 如果需要垂直居中,优先通过更准确的文本框高度、段落行高和 y 坐标解决;布局 wrapper 可以使用,但实际文字节点仍要带显式 `font-size` / `font-weight` / `color`
@@ -309,37 +509,46 @@ Transform 参数同样使用数字或 `px`。不要写 `translate(10%, 20%)`
相邻页面至少改变一个主结构维度:主视觉位置、网格列数、图片用法、文本密度或阅读方向。
### 图片使用
### 图片使用与 Preview Image Mode
默认必须规划和使用图片资产,并规避版权风险。图片必须来自用户提供、公司/项目自有、明确可商用授权图库,或授权条件清晰的 AI 生成资产不要使用版权状态不明的图片、logo、截图、新闻配图、竞品官网图或搜索引擎随手抓取的素材。正确流程是先下载或生成到本地再写成本地占位符
默认必须规划和使用图片资产。Preview 阶段的目标是验证 SVGlide 的视觉表达上限,版权/授权不作为阻断条件;不要因为 license 未确认就退回纯矢量或低信息卡片页。推荐先从用户 query、deck 标题、章节标题和页面 takeaway 生成 2-5 个图片检索词,去网络检索并拉取主题强相关图片;再补充网页截图、产品截图、图库图、新闻/历史/艺术/科普图片、材质纹理或 AI 生成图做占位视觉。必须在 plan / README 里记录 `retrieval_query`、来源 URL或标记 `license=preview_unverified`,并避免明显不适当素材、敏感肖像和会造成商业背书误导的 logo/商标。正式交付时,再统一替换为用户提供、公司/项目自有、明确可商用授权图库,或授权条件清晰的 AI 生成资产。
最稳流程仍然是先下载或生成到本地,再写成本地占位符:
```xml
<image slide:role="image" href="@./assets/hero.jpg" x="0" y="0" width="960" height="540" />
```
图片不只用于局部卡片背景,也可以作为整页背景、半出血主视觉、材质纹理、案例示例、产品截图、数据仪表截图或图鉴封面。作为整页背景时,必须叠加半透明遮罩或暗角,保证标题和正文对比度。
推荐的网络拉图流程:
1. 从用户 query、deck title、page takeaway、章节标题中提取 `retrieval_query`,优先使用具体名词、场景、人物、作品、产品、地点、历史事件或学科对象,避免只搜抽象词。
2. 对封面、章节过渡页、案例页、教学解释页和产品/品牌页优先执行网络图片搜索或网页截图获取,选择和主题直接相关的真实图片,不用无关风景图凑数。
3. 能下载时先保存到 `assets/` 并用 `@./assets/...` 引用;来不及下载时可以先保留 http(s) URL 进入 preview但 live/readback 后必须确认可见。
4. 每张图在 `asset_contract` 记录 `retrieval_query``source_type``source_url``retrieved_at``license=preview_unverified``usage_page``replacement_required=true`
5. 网络不可用或无法找到强相关图片时,才退回 AI 生成图、程序化纹理或纯 SVG 视觉,并在 `risk_flags``network_image_fetch_unavailable`
图片不只用于局部卡片背景,也可以作为整页背景、半出血主视觉、材质纹理、案例示例、产品截图、数据仪表截图、网页/应用界面截图、人物/场景图、图鉴封面、历史/艺术/科学素材或产品细节局部。作为整页背景时,必须叠加半透明遮罩或暗角,保证标题和正文对比度。
图片数量与用法建议:
- MUST: 在 `asset_strategy` 或产物 README 中记录图片来源、授权/许可类型、下载 URL 或生成方式;无法确认授权时不得使用
- MUST: 5 页以上 deck 至少使用 1 张真实图片8 页以上 deck 至少使用 2 张;宣传/产品/品牌/案例型 deck 至少使用 3 张。
- MUST: 在 `asset_strategy` 或产物 README 中记录图片检索词、图片来源、授权/许可类型、下载 URL 或生成方式;Preview 阶段无法确认授权时写 `license=preview_unverified``replacement_required=true`preflight 不阻断,最终交付应替换为可授权资产
- MUST: 5 页以上 deck 至少使用 2 张真实图片8 页以上 deck 至少使用 4 张;宣传/产品/品牌/案例/教学型 deck 至少使用 5或至少 40% 页面含图片
- MUST: 封面优先使用图片或图片+抽象图形混合主视觉,不要只用网格、光效和几何背景。
- MUST: 案例页优先使用行业场景图、产品截图、仪表盘截图或真实质感背景,并叠加数据 callout。
- SHOULD: 同一 deck 中混用全幅背景、半出血图片、卡片图、纹理/材质背景标注型截图,避免所有图片都只是小卡片背景。
- MUST NOT: 保留空图片框、破图、http(s) 外链或 data URL。素材不可用时要重新获取/生成,或在最终说明中明确为什么退回矢量
- MUST: 同一 deck 中混用全幅背景、半出血图片、卡片图、纹理/材质背景标注型截图、图鉴式小图和局部裁切特写,避免所有图片都只是小卡片背景。
- SHOULD: 对教育、历史、艺术、医学、产品讲解等主题,优先用图片建立具象认知:人物、器物、场景、局部特写、对比图、流程截图、资料封面或时间背景图
- MUST NOT: 保留空图片框或破图。Preview/MVP 阶段允许 http(s) 外链或 data URL 先进入 preflight warning但 live/readback 后必须确认可见;正式交付应替换为本地 `@./path` 或 file token。
优先使用这些来源,但每张图仍必须检查并记录具体页面上的授权信息
Preview 阶段优先使用这些来源来快速获得丰富视觉;正式交付时再逐图确认授权、署名和替换计划
| Source | 适合用途 | 规则 |
| Source | 适合用途 | Preview 规则 |
|--------|----------|------|
| Unsplash | 高质量摄影、封面背景、场景图 | 可商用图库;记录图片页 URL 和 license |
| Pexels | 商务、科技、生活类配图 | 可商用图库;记录图片页 URLlicense |
| Pixabay | 图片、插画、视频、音频 | 可商用图库;避开人物/品牌/商标误导 |
| Openverse | CC / Public Domain 搜索 | 每张图 license 不同;按单图要求署名 |
| Wikimedia Commons | 百科、历史、技术、公共领域素材 | 每张图 license 不同;常见需要署名 |
| The Met Open Access | 艺术品、历史图像、文化视觉 | 仅使用 Open Access / CC0 条目 |
| Smithsonian Open Access | 博物馆、科学、历史、2D/3D 资产 | 仅使用 Open Access / CC0 条目 |
| NASA Image and Video Library | 太空、科技、地球、航天视觉 | 避开 NASA 标识商业背书、人物肖像和第三方权利 |
| Web image search / topic query | 和用户 query、页面主题、作品/人物/地点/产品直接相关的真实图片 | 优先使用;记录 `retrieval_query`、图片页 URL 和 `preview_unverified`,正式交付再确认或替换 |
| Unsplash / Pexels / Pixabay | 高质量摄影、封面背景、场景图 | 结合主题 query 检索;记录图片页 URLlicense 可先写 `preview_unverified`,正式交付再确认 |
| Openverse / Wikimedia Commons | 百科、历史、技术、公共领域素材 | 记录单图 URL 和作者/页面preview 可先用,正式交付补 license / attribution |
| The Met / Smithsonian / NASA Open Access | 艺术、科学、历史、航天视觉 | 记录条目 URLpreview 可先用,正式交付确认 Open Access / 第三方权利 |
| 官网 / 产品页 / 新闻图 / 搜索图 | 产品截图、竞品页、事件背景、真实语境 | Preview 可作为视觉占位;必须标记 `license=preview_unverified`,正式交付替换或删去 |
| AI 生成图 / 程序化纹理 | 抽象背景、材质、概念图 | 记录生成方式和提示词摘要;正式交付确认模型/平台授权 |
素材清单建议字段:
@@ -347,11 +556,15 @@ Transform 参数同样使用数字或 `px`。不要写 `translate(10%, 20%)`
{
"local_path": "./assets/hero.jpg",
"source": "Unsplash",
"retrieval_query": "Beethoven Symphony No. 5 concert hall orchestra",
"source_url": "https://...",
"license": "Unsplash License",
"commercial_use": true,
"retrieved_at": "2026-06-08",
"license": "preview_unverified",
"commercial_use": "unknown_in_preview",
"replacement_required": true,
"attribution_required": false,
"notes": "No recognizable trademark or misleading endorsement"
"usage_page": 1,
"notes": "Preview-only visual placeholder; replace or verify license before production delivery"
}
```
@@ -374,8 +587,20 @@ Transform 参数同样使用数字或 `px`。不要写 `translate(10%, 20%)`
- 局部标注、刻度、坐标轴、图例。
- 行业标签、材料纹理、指标卡。
- 路线节点、连接线、层级分区。
- 流程/闭环图旁边补机制表或说明卡,例如“触发条件 / 运营动作 / 衡量指标”,不要把说明句塞进圆形节点内部。
- 小型表格、雷达/柱状/散点等微图表。
### 转换稳定性经验
这些规则来自 live 创建后对比 source SVG 与 readback XML 的结果,属于生成侧必须规避的转换差异:
- `image opacity` 不稳定:本地 SVG 里的 `<image opacity="0.18">` / `<image opacity="0.22">` 可能会在 readback `<img>` 中丢失透明度。MVP preflight 只 warning生成器仍应把淡化效果烘焙进图片本身或使用半透明 shape 遮罩。
- shape opacity 稳定:`rect``circle``path` 等 shape 的 `opacity` 会转换为 XML `alpha`,可用于蒙层、暗角和装饰层。
- circle / ellipse stroke width 不稳定:圆形/椭圆描边可能只保留颜色、不保留宽度。关键外圈使用“外层有色圆 + 内层背景圆”的双 shape ring或用 path 绘制;不要用单个 stroked circle 承载关键视觉。
- dashed stroke 不稳定:`stroke-dasharray` 可能降级,尤其是自定义 path 的虚线闭环。关键路线用短 line segment 或 filled dot markers 手工排布;普通装饰虚线也要经 readback 复核。
- path 会转换为 `type="custom"` 并做 bbox 内坐标归一化,这是预期行为;只要 readback bbox 和视觉位置正确,不算差异。
- 字体会被转换为服务端支持字体,例如 `Noto Sans` / `思源黑体`,因此生成阶段要给 `foreignObject` 留足高度,不要按浏览器本地字体做极限排版。
### 生成后检查
生成脚本或人工复核必须检查:
@@ -393,7 +618,7 @@ Transform 参数同样使用数字或 `px`。不要写 `translate(10%, 20%)`
- 每页是否有明确 takeaway高密度页的视觉结构是否承载信息而不只是装饰。
- 内容页是否避免了“大标题 + 大图 + 2-3 个短 chip”的低信息布局。
- 自称数据、排名、客户、引用、logo 或案例时,是否有来源;没有来源时是否改为定性或假设表达。
- 图片是否已变成本地 `@./path` 或 file token不能保留 http(s) / data URL
- 图片是否足够丰富并可见;如果 Preview/MVP 阶段暂时保留 http(s) / data URL 或 `preview_unverified` 来源,要记录 warning、确认 live/readback 可见,并在正式交付前列出替换项
验证记录建议写回 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json``readback_verification` 字段,并在最终回复中简述:
@@ -403,7 +628,7 @@ Transform 参数同样使用数字或 `px`。不要写 `translate(10%, 20%)`
- Dry-run已确认 create presentation + N 次 /slide。
- Readback实际页数 N / 预期 N未发现空白页、破图或缺失 closing slide。
- 版式:检查 safe area、文本重叠、越界和相邻页版式变化。
- 资产:图片均为本地 @path 自动上传或 file token无 http(s)/data URL
- 资产:Preview 阶段优先丰富图片和 readback 可见性;若保留 http(s)/data URL 或 `preview_unverified` 来源,必须记录 warning。正式交付再替换为本地 @path 自动上传或 file token并补齐授权
```
## 错误处理
@@ -435,7 +660,8 @@ Transform 参数同样使用数字或 `px`。不要写 `translate(10%, 20%)`
1. 本地 preflight 已失败:修对应 SVG 文件,不要调用 live API。
2. live 添加页失败且带 `svglide_error`:按 `type` / `tag_name` / `hint` 收敛 SVG 子集,例如降级复杂 filter、path、CSS 或文本结构。
3. plain XML 在同一路由成功但 SVG 失败:优先确认目标 server lane 是否部署了 SVGlide parser不要盲目重写整套 deck。
4. 已创建 presentation 或部分页面时,默认保留现场并回读确认;是否删除空 presentation 必须单独由用户确认
4. SVG 通过本地 preflight 且失败在第 1 页,服务端只返回 generic `nodeServer invalid param`:优先检查 `lark-cli` 环境、代理和 PPE/BOE lane 是否命中目标 slide server。不要先把已通过协议校验的 deck 改回低质量 SVG
5. 已创建 presentation 或部分页面时,默认保留现场并回读确认;是否删除空 presentation 必须单独由用户确认。
### 编辑已创建的 SVG deck

View File

@@ -6,7 +6,7 @@
## Required Flow
1. 理解用户需求,必要时澄清主题、受众、页数、风格。
1. 理解用户需求,必要时澄清主题、受众、页数、风格。SVGlide 新建 deck 如果用户未说明页数,或只说“一份 slide / 一份 PPT / 做个 slide / 生成一个 slide”等模糊表达默认按 10 页写入 `page_count` / `target_slide_count`,不要仅因页数缺失而停下来追问;只有明确“一页 / 单页 / onepage / one slide / 只要封面”才按 1 页。
2. 如果适合模板,先用 `template_tool.py search` 检索,锁定模板后用 `summarize` 获取主题和页型信息。
3. 选择唯一 plan 目录:`.lark-slides/plan/<deck-or-task-id>/`
4. 先创建目录:`mkdir -p .lark-slides/plan/<deck-or-task-id>`
@@ -67,6 +67,18 @@ Exception:
"accent": "Used only for key numbers, conclusions, or focus markers."
}
},
"style_preset": "raw_grid",
"style_selection_reason": "raw_grid fits technical training pages that need dense but readable visual structure",
"style_system": {
"palette": {
"background": "#F5F5F5",
"text": "#0A0A0A",
"accent": "#F2D4CF"
},
"typography": "strong title, readable native text labels",
"background_strategy": "muted grid panels with one stable background family",
"motif": "dense grid panels with restrained accent labels"
},
"typography_constraints": {
"title_max_lines": 2,
"body_max_lines_per_box": 2,
@@ -107,6 +119,9 @@ Top-level fields:
- `audience`: target readers or listeners and their assumed background.
- `theme_style`: visual tone, palette direction, and professional style.
- `visual_system`: deck-level visual rules that must stay stable across pages, including background strategy, recurring motif, and color roles.
- `style_preset`: required for SVGlide SVG decks. Choose one id from `references/style-presets.json`; omit only for non-SVG XML/SXSD plans.
- `style_selection_reason`: required for SVGlide SVG decks. Explain why the preset fits the audience, topic, density, and expected tone.
- `style_system`: required for SVGlide SVG decks. Translate the selected preset into concrete palette, typography, background strategy, and motif rules. This is separate from `visual_system`: `visual_system` describes the deck identity, while `style_system` records the executable style preset translation.
- `typography_constraints`: deck-level limits for line count, text box density, and how to handle long text before XML generation.
- `verification_plan`: explicit checks to perform after creation or major edits; include background consistency, text fit, visual focus, and asset rendering when relevant.
- `slides`: ordered page plans.
@@ -122,6 +137,14 @@ Each slide must include:
- `text_density`: `low`, `medium`, or `high`.
- `speaker_intent`: why the speaker needs this page and how it advances the story.
SVGlide SVG slides must also include:
- `visual_recipe`: the SVG-native page recipe, such as `path_flow`, `technical_texture`, or `fake_ui_dashboard`.
- `visual_signature`: the page's distinctive SVG visual memory point compared with a normal XML/PPT template.
- `svg_effects`: canonical effect names actually used or planned, such as `path`, `connector_flow`, `gradient`, `texture`, `chart_geometry`, or `image_overlay`.
- `required_primitives` and `svg_primitives`: the planned SVGlide-safe primitives that must be present in the SVG source.
- `xml_like_risk`, `content_density_contract`, `risk_flags`, and `source_policy`: quality and source-safety fields consumed by `svg_preflight.py --plan`.
## Layout Vocabulary
Use one of these `layout_type` values unless the user explicitly needs a custom structure:

View File

@@ -0,0 +1,542 @@
{
"version": "2026-06-10",
"source": "beautiful-feishu-whiteboard",
"canvas": "960x540",
"selection_rule": [
"Choose intensity first: Restrained for quiet/formal decks, Balanced for most business or training decks, Bold for poster-like or high-energy decks.",
"Use preset style tokens to shape palette, panel treatment, connector density, typography scale, and texture.",
"Do not copy raw whiteboard nodes, raw coordinates, source prompts, source file paths, tool names, source tokens, or preset names into visible slide content."
],
"groups": {
"Restrained": {"expected_count": 9, "use_when": "Serious, quiet, editorial, institutional, or text-first decks."},
"Balanced": {"expected_count": 15, "use_when": "General business, technical, educational, and explanatory decks."},
"Bold": {"expected_count": 11, "use_when": "Posters, showcases, events, playful explainers, and high-energy visual impact."}
},
"presets": [
{
"style_id": "avocado_press",
"display_name": "Avocado Press",
"group": "Restrained",
"source_token": "TIBNwZ6fLhfPh1bZlAQuFRnFswW",
"formality": "high",
"vibe": ["editorial", "fresh", "structured"],
"best_for": ["agenda.structured", "process.flow", "quote.insight"],
"avoid_for": ["cover.hero"],
"palette": {"background": "#FFFFFF", "text": "#1F2329", "muted": "#0055A4", "accent": "#DCF4A2", "support": ["#0055A4"]},
"shape_language": {"panel_treatment": "editorial blocks with bright accent labels", "corner_radius": "low", "border_weight": "medium", "texture": "clean print-like structure"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "high", "node_budget": {"source_nodes": 59, "source_text": 39, "source_shapes": 9, "source_connectors": 11}},
"slide_translation": {"recommended_layouts": ["agenda", "process", "quote"], "svglide_primitives": ["typography", "geometric_shape", "connector_flow"], "fallback_policy": "Keep text native and use explicit connector lines for process structure."},
"quality_oracle": {"expected_style_signals": ["blue structural labels", "avocado accent", "editorial spacing"], "warning_thresholds": {"text_boxes_max": 24, "accent_colors_max": 2}}
},
{
"style_id": "grove",
"display_name": "Grove",
"group": "Restrained",
"source_token": "IOCVwTYCYhhUj9bbkAwuncDTslf",
"formality": "high",
"vibe": ["institutional", "organic", "calm"],
"best_for": ["architecture.layered", "section.divider", "process.flow"],
"avoid_for": ["brand_system"],
"palette": {"background": "#E8E4D6", "text": "#192B1B", "muted": "#D4CFBF", "accent": "#C8524A", "support": ["#DEDAD0"]},
"shape_language": {"panel_treatment": "soft editorial panels with deep green hierarchy", "corner_radius": "low", "border_weight": "light", "texture": "warm paper bands"},
"density": {"text_density": "medium", "label_density": "high", "connector_density": "low", "node_budget": {"source_nodes": 62, "source_text": 44, "source_shapes": 13, "source_connectors": 2}},
"slide_translation": {"recommended_layouts": ["architecture", "section", "process"], "svglide_primitives": ["typography", "geometric_shape", "annotation"], "fallback_policy": "Use grouped green panels and sparse connectors rather than dense arrows."},
"quality_oracle": {"expected_style_signals": ["deep green title mass", "muted paper background", "small warm accent"], "warning_thresholds": {"text_boxes_max": 26, "accent_colors_max": 2}}
},
{
"style_id": "jade_lens",
"display_name": "Jade Lens",
"group": "Restrained",
"source_token": "T0eswEvY1h6uSZbbt1FujZp0sZf",
"formality": "high",
"vibe": ["research", "clean", "knowledge"],
"best_for": ["timeline.roadmap", "architecture.layered", "agenda.structured"],
"avoid_for": ["cover.hero"],
"palette": {"background": "#F5F1EE", "text": "#0E5A3C", "muted": "#2BA483", "accent": "#2CAE8C", "support": ["#FFFFFF"]},
"shape_language": {"panel_treatment": "jade framed cards with layered labels", "corner_radius": "medium", "border_weight": "medium", "texture": "lens-like panels"},
"density": {"text_density": "medium", "label_density": "high", "connector_density": "medium", "node_budget": {"source_nodes": 72, "source_text": 44, "source_shapes": 19, "source_connectors": 9}},
"slide_translation": {"recommended_layouts": ["timeline", "architecture", "agenda"], "svglide_primitives": ["typography", "geometric_shape", "connector_flow"], "fallback_policy": "Keep jade labels as native text; simplify nested frames when crowded."},
"quality_oracle": {"expected_style_signals": ["jade green framing", "white content panels", "medium connector scaffolding"], "warning_thresholds": {"text_boxes_max": 28, "accent_colors_max": 2}}
},
{
"style_id": "long_table",
"display_name": "Long Table",
"group": "Restrained",
"source_token": "VrJhwVUTwhjU2zbBpI7uEjy2szg",
"formality": "high",
"vibe": ["procedural", "responsibility", "planning"],
"best_for": ["table.visual-summary", "process.flow", "timeline.roadmap"],
"avoid_for": ["cover.hero"],
"palette": {"background": "#FAF1E2", "text": "#1F2329", "muted": "#F2E5CF", "accent": "#B53D2A", "support": ["#FFFFFF"]},
"shape_language": {"panel_treatment": "long horizontal table bands with clear separators", "corner_radius": "low", "border_weight": "medium", "texture": "tabular rows"},
"density": {"text_density": "high", "label_density": "high", "connector_density": "high", "node_budget": {"source_nodes": 69, "source_text": 40, "source_shapes": 9, "source_connectors": 17}},
"slide_translation": {"recommended_layouts": ["table", "process", "timeline"], "svglide_primitives": ["typography", "geometric_shape", "connector_flow"], "fallback_policy": "Prefer table/grid native shapes; connector count can be reduced if slide becomes cramped."},
"quality_oracle": {"expected_style_signals": ["long horizontal rows", "red emphasis", "explicit flow connectors"], "warning_thresholds": {"text_boxes_max": 28, "accent_colors_max": 2}}
},
{
"style_id": "macchiato",
"display_name": "Macchiato",
"group": "Restrained",
"source_token": "Jhl9w3gZghgXzeb6WLwu44VXsMg",
"formality": "high",
"vibe": ["quiet", "editorial", "warm"],
"best_for": ["quote.insight", "section.divider", "comparison.two-column"],
"avoid_for": ["dashboard.kpi-grid"],
"palette": {"background": "#EDE7DD", "text": "#25211B", "muted": "#9A917F", "accent": "#6E6558", "support": ["#FFFFFF"]},
"shape_language": {"panel_treatment": "minimal editorial blocks with low contrast", "corner_radius": "low", "border_weight": "light", "texture": "coffee paper tone"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "low", "node_budget": {"source_nodes": 48, "source_text": 37, "source_shapes": 5, "source_connectors": 6}},
"slide_translation": {"recommended_layouts": ["quote", "section", "comparison"], "svglide_primitives": ["typography", "geometric_shape", "annotation"], "fallback_policy": "Use whitespace and typographic hierarchy instead of many decorative nodes."},
"quality_oracle": {"expected_style_signals": ["low contrast warm neutrals", "large quiet text", "few panels"], "warning_thresholds": {"text_boxes_max": 20, "accent_colors_max": 1}}
},
{
"style_id": "monochrome",
"display_name": "Monochrome",
"group": "Restrained",
"source_token": "ApDnwnul9hlwg8b4Jl1uDweAs2c",
"formality": "high",
"vibe": ["serious", "technical", "minimal"],
"best_for": ["architecture.layered", "quote.insight", "agenda.structured"],
"avoid_for": ["brand_system"],
"palette": {"background": "#FAFADF", "text": "#1A1A16", "muted": "#8A8A80", "accent": "#5E5E54", "support": ["#FFFFFF"]},
"shape_language": {"panel_treatment": "monochrome rules and restrained blocks", "corner_radius": "low", "border_weight": "light", "texture": "minimal editorial grid"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "medium", "node_budget": {"source_nodes": 48, "source_text": 37, "source_shapes": 3, "source_connectors": 8}},
"slide_translation": {"recommended_layouts": ["architecture", "quote", "agenda"], "svglide_primitives": ["typography", "geometric_shape", "connector_flow"], "fallback_policy": "Rely on black/gray hierarchy and simple connectors; avoid fake color accents."},
"quality_oracle": {"expected_style_signals": ["black text hierarchy", "single neutral accent", "minimal blocks"], "warning_thresholds": {"text_boxes_max": 22, "accent_colors_max": 1}}
},
{
"style_id": "papier_bleu",
"display_name": "Papier Bleu",
"group": "Restrained",
"source_token": "HWi5woaS8h1D4EbKutnulYWdsWc",
"formality": "medium",
"vibe": ["blueprint", "clear", "knowledge"],
"best_for": ["process.flow", "timeline.roadmap", "image.story"],
"avoid_for": ["brand_system"],
"palette": {"background": "#FAF3EB", "text": "#1A3C8F", "muted": "#4FB8D8", "accent": "#72D0E9", "support": ["#FFFFFF"]},
"shape_language": {"panel_treatment": "blue paper panels with airy blocks", "corner_radius": "medium", "border_weight": "medium", "texture": "light blueprint geometry"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "low", "node_budget": {"source_nodes": 64, "source_text": 38, "source_shapes": 22, "source_connectors": 4}},
"slide_translation": {"recommended_layouts": ["process", "timeline", "image"], "svglide_primitives": ["typography", "geometric_shape", "annotation"], "fallback_policy": "Use blue diagram cards and short labels; avoid overusing cyan fills."},
"quality_oracle": {"expected_style_signals": ["blue structural panels", "cream paper base", "clear diagram labels"], "warning_thresholds": {"text_boxes_max": 24, "accent_colors_max": 2}}
},
{
"style_id": "reading_room",
"display_name": "Reading Room",
"group": "Restrained",
"source_token": "Wx8Ow5ThFhDn5Lb1VFzu5bDEskD",
"formality": "high",
"vibe": ["paper", "training", "reading"],
"best_for": ["quote.insight", "section.divider", "paper.explainer"],
"avoid_for": ["fake_ui_dashboard"],
"palette": {"background": "#F6EBD8", "text": "#0B0A09", "muted": "#F1DAB1", "accent": "#DE916A", "support": ["#D6C7CC"]},
"shape_language": {"panel_treatment": "reading-card panels with warm paper blocks", "corner_radius": "low", "border_weight": "light", "texture": "bookish paper"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "medium", "node_budget": {"source_nodes": 54, "source_text": 38, "source_shapes": 6, "source_connectors": 7}},
"slide_translation": {"recommended_layouts": ["quote", "section", "paper"], "svglide_primitives": ["typography", "geometric_shape", "annotation"], "fallback_policy": "Use warm panels and pull quotes; keep connectors secondary."},
"quality_oracle": {"expected_style_signals": ["book paper background", "warm orange accent", "quiet reading hierarchy"], "warning_thresholds": {"text_boxes_max": 24, "accent_colors_max": 2}}
},
{
"style_id": "salmon_stamp",
"display_name": "Salmon Stamp",
"group": "Restrained",
"source_token": "IITLwQQ7Vhj0lzbBmhvuvtDWs4c",
"formality": "medium",
"vibe": ["stamp", "training", "emphasis"],
"best_for": ["section.divider", "kpi.big-number", "agenda.structured"],
"avoid_for": ["technical_texture"],
"palette": {"background": "#FFFFFF", "text": "#000000", "muted": "#F0AE9E", "accent": "#049550", "support": ["#F0AE9E"]},
"shape_language": {"panel_treatment": "stamp-like salmon labels with green accents", "corner_radius": "low", "border_weight": "medium", "texture": "print stamp blocks"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "high", "node_budget": {"source_nodes": 60, "source_text": 40, "source_shapes": 9, "source_connectors": 11}},
"slide_translation": {"recommended_layouts": ["section", "kpi", "agenda"], "svglide_primitives": ["typography", "geometric_shape", "connector_flow"], "fallback_policy": "Keep stamp labels editable; use green only for key emphasis."},
"quality_oracle": {"expected_style_signals": ["salmon blocks", "black type", "green action accent"], "warning_thresholds": {"text_boxes_max": 25, "accent_colors_max": 2}}
},
{
"style_id": "apricot_arc",
"display_name": "Apricot Arc",
"group": "Balanced",
"source_token": "JvQewyVpphPc2MbOD4xuiTD5sbd",
"formality": "medium",
"vibe": ["warm", "motion", "roadmap"],
"best_for": ["timeline.roadmap", "process.flow", "cover.hero"],
"avoid_for": ["monochrome.audit"],
"palette": {"background": "#FFF8EE", "text": "#7A4A33", "muted": "#F9C2BD", "accent": "#C7561E", "support": ["#F69834"]},
"shape_language": {"panel_treatment": "warm arcs and rounded progression panels", "corner_radius": "medium", "border_weight": "medium", "texture": "arc motion geometry"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "high", "node_budget": {"source_nodes": 76, "source_text": 37, "source_shapes": 23, "source_connectors": 16}},
"slide_translation": {"recommended_layouts": ["timeline", "process", "cover"], "svglide_primitives": ["path", "connector_flow", "geometric_shape"], "fallback_policy": "Use explicit arc paths and staged labels; simplify connectors if crowded."},
"quality_oracle": {"expected_style_signals": ["apricot arcs", "warm motion path", "stage progression"], "warning_thresholds": {"text_boxes_max": 25, "accent_colors_max": 3}}
},
{
"style_id": "berry_pop",
"display_name": "Berry Pop",
"group": "Balanced",
"source_token": "JAcFwmlcIh8NKNbJOzGupeTKscg",
"formality": "medium",
"vibe": ["business", "soft", "case"],
"best_for": ["comparison.two-column", "image.story", "dashboard.kpi-grid"],
"avoid_for": ["legal.audit"],
"palette": {"background": "#EDF0FA", "text": "#6E1E3A", "muted": "#C7D2F0", "accent": "#9E2B50", "support": ["#9DB0E8"]},
"shape_language": {"panel_treatment": "soft berry panels with cool support areas", "corner_radius": "medium", "border_weight": "light", "texture": "soft editorial contrast"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "medium", "node_budget": {"source_nodes": 65, "source_text": 40, "source_shapes": 13, "source_connectors": 12}},
"slide_translation": {"recommended_layouts": ["comparison", "image", "dashboard"], "svglide_primitives": ["typography", "geometric_shape", "annotation"], "fallback_policy": "Use berry for key claims and blue support panels for evidence."},
"quality_oracle": {"expected_style_signals": ["berry headline", "cool soft panels", "balanced contrast"], "warning_thresholds": {"text_boxes_max": 25, "accent_colors_max": 3}}
},
{
"style_id": "bold_poster",
"display_name": "Bold Poster",
"group": "Balanced",
"source_token": "TOwewbtxrhmxNgbzko3udFBvsBd",
"formality": "medium",
"vibe": ["poster", "direct", "high-contrast"],
"best_for": ["cover.hero", "quote.insight", "section.divider"],
"avoid_for": ["dense.table"],
"palette": {"background": "#F5F2EF", "text": "#1C1410", "muted": "#FFFFFF", "accent": "#D8000F", "support": ["#1C1410"]},
"shape_language": {"panel_treatment": "poster blocks with strong red hits", "corner_radius": "low", "border_weight": "heavy", "texture": "flat poster"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "low", "node_budget": {"source_nodes": 52, "source_text": 40, "source_shapes": 6, "source_connectors": 6}},
"slide_translation": {"recommended_layouts": ["cover", "quote", "section"], "svglide_primitives": ["typography", "geometric_shape", "annotation"], "fallback_policy": "Use large type and one strong red visual anchor; avoid dense micro text."},
"quality_oracle": {"expected_style_signals": ["red poster accent", "black mass", "large type"], "warning_thresholds": {"text_boxes_max": 20, "accent_colors_max": 2}}
},
{
"style_id": "checker_bloom",
"display_name": "Checker Bloom",
"group": "Balanced",
"source_token": "S5tEwSOiAhbuvObGnNkuCAUps8d",
"formality": "medium",
"vibe": ["grid", "capability", "modular"],
"best_for": ["dashboard.kpi-grid", "icon_capability_map", "table.visual-summary"],
"avoid_for": ["quote.insight"],
"palette": {"background": "#E8F1DD", "text": "#151515", "muted": "#5E9E4A", "accent": "#2C6EE0", "support": ["#FFFFFF"]},
"shape_language": {"panel_treatment": "checker grid cells with bloom accents", "corner_radius": "medium", "border_weight": "medium", "texture": "modular checker geometry"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "medium", "node_budget": {"source_nodes": 73, "source_text": 37, "source_shapes": 20, "source_connectors": 12}},
"slide_translation": {"recommended_layouts": ["dashboard", "capability_map", "table"], "svglide_primitives": ["geometric_shape", "icon", "micro_chart"], "fallback_policy": "Build true grid modules; do not reduce to three generic cards."},
"quality_oracle": {"expected_style_signals": ["checker grid", "blue-green accents", "capability modules"], "warning_thresholds": {"text_boxes_max": 26, "accent_colors_max": 3}}
},
{
"style_id": "cobalt_bloom",
"display_name": "Cobalt Bloom",
"group": "Balanced",
"source_token": "Ts2iwaXOuhOBkYbKD80uLjyCsje",
"formality": "medium",
"vibe": ["modern", "tech", "status"],
"best_for": ["architecture.layered", "fake_ui_dashboard", "cover.hero"],
"avoid_for": ["legal.audit"],
"palette": {"background": "#F4EFE9", "text": "#171717", "muted": "#DDA8A2", "accent": "#4746C6", "support": ["#CE968F"]},
"shape_language": {"panel_treatment": "modern cobalt panels with soft pink supports", "corner_radius": "medium", "border_weight": "medium", "texture": "tech editorial blocks"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "low", "node_budget": {"source_nodes": 56, "source_text": 40, "source_shapes": 11, "source_connectors": 5}},
"slide_translation": {"recommended_layouts": ["architecture", "dashboard", "cover"], "svglide_primitives": ["typography", "geometric_shape", "dashboard"], "fallback_policy": "Use cobalt as structural system color and pink as secondary highlight."},
"quality_oracle": {"expected_style_signals": ["cobalt blocks", "modern soft support panels", "technical editorial tone"], "warning_thresholds": {"text_boxes_max": 24, "accent_colors_max": 3}}
},
{
"style_id": "coral",
"display_name": "Coral",
"group": "Balanced",
"source_token": "Ez05w4JTahrMIjb6hjcuJRDpsOy",
"formality": "medium",
"vibe": ["report", "warm", "clear"],
"best_for": ["agenda.structured", "comparison.two-column", "kpi.big-number"],
"avoid_for": ["monochrome.audit"],
"palette": {"background": "#F5F0E8", "text": "#1A1A1A", "muted": "#6B6B6B", "accent": "#E85D5D", "support": ["#D44A4A"]},
"shape_language": {"panel_treatment": "coral emphasis cards with neutral body text", "corner_radius": "medium", "border_weight": "light", "texture": "warm report blocks"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "low", "node_budget": {"source_nodes": 68, "source_text": 37, "source_shapes": 23, "source_connectors": 5}},
"slide_translation": {"recommended_layouts": ["agenda", "comparison", "kpi"], "svglide_primitives": ["typography", "geometric_shape", "micro_chart"], "fallback_policy": "Use coral only for claims, values, or section anchors."},
"quality_oracle": {"expected_style_signals": ["coral claim blocks", "neutral report structure", "clear emphasis hierarchy"], "warning_thresholds": {"text_boxes_max": 25, "accent_colors_max": 2}}
},
{
"style_id": "cut_bloom",
"display_name": "Cut Bloom",
"group": "Balanced",
"source_token": "QHtIwZ4aeha6q8bDkfiuU7TCsgb",
"formality": "medium",
"vibe": ["geometric", "sectioned", "structured"],
"best_for": ["geometric_composition", "architecture.layered", "section.divider"],
"avoid_for": ["dense.table"],
"palette": {"background": "#FFFFFF", "text": "#2E3566", "muted": "#535D9E", "accent": "#F0CB65", "support": ["#FFFFFF"]},
"shape_language": {"panel_treatment": "cut color planes and strong section blocks", "corner_radius": "low", "border_weight": "medium", "texture": "cut-paper geometry"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "low", "node_budget": {"source_nodes": 68, "source_text": 40, "source_shapes": 26, "source_connectors": 2}},
"slide_translation": {"recommended_layouts": ["geometric", "architecture", "section"], "svglide_primitives": ["path", "geometric_shape", "typography"], "fallback_policy": "Translate angled blocks into safe path geometry, not polygons."},
"quality_oracle": {"expected_style_signals": ["cut planes", "navy structure", "yellow highlight"], "warning_thresholds": {"text_boxes_max": 24, "accent_colors_max": 3}}
},
{
"style_id": "editorial_forest",
"display_name": "Editorial Forest",
"group": "Balanced",
"source_token": "DeJQwotDFhKucHbVvdUufxepsAe",
"formality": "high",
"vibe": ["research", "editorial", "ecosystem"],
"best_for": ["quote.insight", "comparison.two-column", "paper.explainer"],
"avoid_for": ["brand_system"],
"palette": {"background": "#EFE7D4", "text": "#1A1A17", "muted": "#243A21", "accent": "#E89CB1", "support": ["#2E4A2A"]},
"shape_language": {"panel_treatment": "forest editorial blocks with pink accent", "corner_radius": "low", "border_weight": "light", "texture": "journal-like composition"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "low", "node_budget": {"source_nodes": 51, "source_text": 39, "source_shapes": 6, "source_connectors": 6}},
"slide_translation": {"recommended_layouts": ["quote", "comparison", "paper"], "svglide_primitives": ["typography", "geometric_shape", "annotation"], "fallback_policy": "Let text hierarchy carry the page; use pink accent sparingly."},
"quality_oracle": {"expected_style_signals": ["forest green editorial base", "pink accent", "quiet paper field"], "warning_thresholds": {"text_boxes_max": 22, "accent_colors_max": 2}}
},
{
"style_id": "lime_slab",
"display_name": "Lime Slab",
"group": "Balanced",
"source_token": "T3oLwlQLohw8G7b4qfJuFqw9syd",
"formality": "medium",
"vibe": ["energetic", "technical", "slab"],
"best_for": ["cover.hero", "architecture.layered", "process.flow"],
"avoid_for": ["quiet.executive"],
"palette": {"background": "#FFFFF2", "text": "#0A0A05", "muted": "#2F2E25", "accent": "#EEFA79", "support": ["#FFFFFF"]},
"shape_language": {"panel_treatment": "thick slab panels with lime highlight", "corner_radius": "low", "border_weight": "heavy", "texture": "high-energy slab geometry"},
"density": {"text_density": "high", "label_density": "high", "connector_density": "medium", "node_budget": {"source_nodes": 82, "source_text": 43, "source_shapes": 28, "source_connectors": 11}},
"slide_translation": {"recommended_layouts": ["cover", "architecture", "process"], "svglide_primitives": ["typography", "geometric_shape", "connector_flow"], "fallback_policy": "Use lime as high-energy focal surface, but keep text boxes large."},
"quality_oracle": {"expected_style_signals": ["lime slab title", "heavy black structure", "dense technical labels"], "warning_thresholds": {"text_boxes_max": 28, "accent_colors_max": 2}}
},
{
"style_id": "linen_cut",
"display_name": "Linen Cut",
"group": "Balanced",
"source_token": "WI3Yw4BakhfaoNbiEfRuCwq0slg",
"formality": "medium",
"vibe": ["business", "linen", "structured"],
"best_for": ["agenda.structured", "table.visual-summary", "comparison.two-column"],
"avoid_for": ["cover.hero"],
"palette": {"background": "#E4D2C4", "text": "#1F1A14", "muted": "#044D99", "accent": "#F61B27", "support": ["#04B24F"]},
"shape_language": {"panel_treatment": "linen panels with sharp color tabs", "corner_radius": "low", "border_weight": "medium", "texture": "woven warm base"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "low", "node_budget": {"source_nodes": 54, "source_text": 37, "source_shapes": 14, "source_connectors": 3}},
"slide_translation": {"recommended_layouts": ["agenda", "table", "comparison"], "svglide_primitives": ["typography", "geometric_shape", "annotation"], "fallback_policy": "Keep red/green/blue as role colors, not decoration."},
"quality_oracle": {"expected_style_signals": ["linen base", "sharp red/green/blue tabs", "business grid"], "warning_thresholds": {"text_boxes_max": 24, "accent_colors_max": 3}}
},
{
"style_id": "pin_paper",
"display_name": "Pin & Paper",
"group": "Balanced",
"source_token": "NZ5bwYLs1hAat1bHo29uDdhSsJx",
"formality": "medium",
"vibe": ["workshop", "action", "paper"],
"best_for": ["agenda.structured", "process.flow", "quote.insight"],
"avoid_for": ["legal.audit"],
"palette": {"background": "#FFFFFF", "text": "#1F2329", "muted": "#2A3C99", "accent": "#F1E84E", "support": ["#FFFFFF"]},
"shape_language": {"panel_treatment": "pinned paper notes with blue rails", "corner_radius": "medium", "border_weight": "medium", "texture": "workshop paper"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "low", "node_budget": {"source_nodes": 66, "source_text": 40, "source_shapes": 21, "source_connectors": 2}},
"slide_translation": {"recommended_layouts": ["agenda", "process", "quote"], "svglide_primitives": ["typography", "geometric_shape", "annotation"], "fallback_policy": "Use pinned note panels and explicit action labels; avoid showing the preset name."},
"quality_oracle": {"expected_style_signals": ["pinned paper cards", "blue rails", "yellow highlight"], "warning_thresholds": {"text_boxes_max": 25, "accent_colors_max": 3}}
},
{
"style_id": "raw_grid",
"display_name": "Raw Grid",
"group": "Balanced",
"source_token": "Z6CbwF2oPhvXQ8biBRnufsyksqf",
"formality": "medium",
"vibe": ["technical", "dense", "grid"],
"best_for": ["dashboard.kpi-grid", "table.visual-summary", "architecture.layered"],
"avoid_for": ["cover.hero"],
"palette": {"background": "#F5F5F5", "text": "#0A0A0A", "muted": "#333333", "accent": "#F2D4CF", "support": ["#FFFFFF", "#E5EDD6"]},
"shape_language": {"panel_treatment": "grid panels with dense labels", "corner_radius": "low", "border_weight": "medium", "texture": "explicit grid geometry"},
"density": {"text_density": "high", "label_density": "high", "connector_density": "low", "node_budget": {"source_nodes": 98, "source_text": 52, "source_shapes": 44, "source_connectors": 2}},
"slide_translation": {"recommended_layouts": ["dashboard", "table", "architecture"], "svglide_primitives": ["geometric_shape", "typography", "texture", "annotation"], "fallback_policy": "Keep native text and basic shapes; fallback only for over-dense visual tables."},
"quality_oracle": {"expected_style_signals": ["grid geometry", "dense labels", "muted panels"], "warning_thresholds": {"text_boxes_max": 28, "accent_colors_max": 2}}
},
{
"style_id": "riptide_cobalt",
"display_name": "Riptide Cobalt",
"group": "Balanced",
"source_token": "Qpyow1AnZhU763b3d51ur42csZd",
"formality": "medium",
"vibe": ["technology", "flow", "cobalt"],
"best_for": ["path_flow", "architecture.layered", "fake_ui_dashboard"],
"avoid_for": ["quiet.executive"],
"palette": {"background": "#FDF0E0", "text": "#1A2240", "muted": "#2741C0", "accent": "#375DFE", "support": ["#FFFFFF"]},
"shape_language": {"panel_treatment": "cobalt panels with flowing technical emphasis", "corner_radius": "medium", "border_weight": "medium", "texture": "blue flow geometry"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "low", "node_budget": {"source_nodes": 69, "source_text": 41, "source_shapes": 26, "source_connectors": 2}},
"slide_translation": {"recommended_layouts": ["path", "architecture", "dashboard"], "svglide_primitives": ["path", "geometric_shape", "dashboard"], "fallback_policy": "Use cobalt flow paths and layered blocks; do not overdo connectors."},
"quality_oracle": {"expected_style_signals": ["cobalt flow", "cream contrast", "layered tech panels"], "warning_thresholds": {"text_boxes_max": 26, "accent_colors_max": 3}}
},
{
"style_id": "soft_editorial",
"display_name": "Soft Editorial",
"group": "Balanced",
"source_token": "T7A4w3ioHhgOjLbuFywuqZwbsUg",
"formality": "medium",
"vibe": ["soft", "editorial", "summary"],
"best_for": ["quote.insight", "agenda.structured", "comparison.two-column"],
"avoid_for": ["technical_texture"],
"palette": {"background": "#ECE9DC", "text": "#1C1A17", "muted": "#E7C6AD", "accent": "#E2A8CE", "support": ["#C9DA4F"]},
"shape_language": {"panel_treatment": "soft editorial fields with small text fragments", "corner_radius": "medium", "border_weight": "light", "texture": "gentle magazine layout"},
"density": {"text_density": "medium", "label_density": "high", "connector_density": "low", "node_budget": {"source_nodes": 51, "source_text": 40, "source_shapes": 5, "source_connectors": 6}},
"slide_translation": {"recommended_layouts": ["quote", "agenda", "comparison"], "svglide_primitives": ["typography", "geometric_shape", "annotation"], "fallback_policy": "Split text into readable editorial fragments; keep labels concise."},
"quality_oracle": {"expected_style_signals": ["soft editorial colors", "small text fragments", "gentle accent palette"], "warning_thresholds": {"text_boxes_max": 24, "accent_colors_max": 3}}
},
{
"style_id": "violet_marker",
"display_name": "Violet Marker",
"group": "Balanced",
"source_token": "HISlwkosLhVgFVb8y3KucknAslb",
"formality": "medium",
"vibe": ["marker", "training", "concept"],
"best_for": ["section.divider", "process.flow", "icon_capability_map"],
"avoid_for": ["legal.audit"],
"palette": {"background": "#FFFFFF", "text": "#000000", "muted": "#666463", "accent": "#C5A1FF", "support": ["#CFEE30"]},
"shape_language": {"panel_treatment": "marker-like labels with violet and lime accents", "corner_radius": "medium", "border_weight": "medium", "texture": "hand-highlighted marker blocks"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "high", "node_budget": {"source_nodes": 59, "source_text": 39, "source_shapes": 9, "source_connectors": 11}},
"slide_translation": {"recommended_layouts": ["section", "process", "capability_map"], "svglide_primitives": ["typography", "geometric_shape", "connector_flow"], "fallback_policy": "Use violet/lime marker tags for concepts, not all text."},
"quality_oracle": {"expected_style_signals": ["violet marker accent", "lime support", "concept labels"], "warning_thresholds": {"text_boxes_max": 25, "accent_colors_max": 3}}
},
{
"style_id": "blockframe",
"display_name": "BlockFrame",
"group": "Bold",
"source_token": "Qu3Lwf4VRhyYzWbjI1xuBn5UsEc",
"formality": "low",
"vibe": ["showcase", "blocky", "colorful"],
"best_for": ["cover.hero", "brand_system", "image.story"],
"avoid_for": ["legal.audit"],
"palette": {"background": "#FFFFFF", "text": "#000000", "muted": "#C0F7FE", "accent": "#F7CB46", "support": ["#FE90E8", "#99E885", "#FFDC8B"]},
"shape_language": {"panel_treatment": "strong colored block frames", "corner_radius": "low", "border_weight": "heavy", "texture": "graphic frame system"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "low", "node_budget": {"source_nodes": 75, "source_text": 41, "source_shapes": 31, "source_connectors": 2}},
"slide_translation": {"recommended_layouts": ["cover", "brand", "image"], "svglide_primitives": ["geometric_shape", "typography", "brand_system"], "fallback_policy": "Use block frames as visual identity; keep content hierarchy simple."},
"quality_oracle": {"expected_style_signals": ["multi-color frames", "black outlines", "bold block identity"], "warning_thresholds": {"text_boxes_max": 24, "accent_colors_max": 5}}
},
{
"style_id": "burst_panel",
"display_name": "Burst Panel",
"group": "Bold",
"source_token": "IUVZwJZirhaIZQbxkfnuj2jPskh",
"formality": "low",
"vibe": ["poster", "burst", "dense"],
"best_for": ["cover.hero", "dashboard.kpi-grid", "brand_system"],
"avoid_for": ["quiet.executive"],
"palette": {"background": "#FFFAF0", "text": "#1E1E1E", "muted": "#FBD65A", "accent": "#FFA76D", "support": ["#CFACE8", "#AAE4BA"]},
"shape_language": {"panel_treatment": "bursting panels with many colored regions", "corner_radius": "medium", "border_weight": "medium", "texture": "poster panel collage"},
"density": {"text_density": "high", "label_density": "high", "connector_density": "low", "node_budget": {"source_nodes": 89, "source_text": 50, "source_shapes": 37, "source_connectors": 2}},
"slide_translation": {"recommended_layouts": ["cover", "dashboard", "brand"], "svglide_primitives": ["geometric_shape", "typography", "micro_chart"], "fallback_policy": "Preserve high-energy panels but cap text boxes to avoid crowding."},
"quality_oracle": {"expected_style_signals": ["burst panels", "warm poster palette", "high information blocks"], "warning_thresholds": {"text_boxes_max": 30, "accent_colors_max": 5}}
},
{
"style_id": "confetti_wedge",
"display_name": "Confetti Wedge",
"group": "Bold",
"source_token": "YDyLwTCiHhKJ0NbuP89uHUV8sNh",
"formality": "low",
"vibe": ["playful", "light", "wedge"],
"best_for": ["section.divider", "image.story", "quote.insight"],
"avoid_for": ["dense.table"],
"palette": {"background": "#F4F8FB", "text": "#000000", "muted": "#3A3C3E", "accent": "#62C0A5", "support": ["#F8BED4", "#65C8CD"]},
"shape_language": {"panel_treatment": "confetti wedges and light motion shapes", "corner_radius": "medium", "border_weight": "light", "texture": "playful wedge accents"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "low", "node_budget": {"source_nodes": 52, "source_text": 37, "source_shapes": 13, "source_connectors": 2}},
"slide_translation": {"recommended_layouts": ["section", "image", "quote"], "svglide_primitives": ["path", "geometric_shape", "typography"], "fallback_policy": "Use a few wedge paths as memory points; avoid confetti over text."},
"quality_oracle": {"expected_style_signals": ["wedge accents", "light playful palette", "simple panel structure"], "warning_thresholds": {"text_boxes_max": 22, "accent_colors_max": 4}}
},
{
"style_id": "court_press",
"display_name": "Court Press",
"group": "Bold",
"source_token": "JH9owJl4sh0PeIbUm9SujYCTsTc",
"formality": "low",
"vibe": ["sports", "team", "operations"],
"best_for": ["process.flow", "timeline.roadmap", "metaphor_loop"],
"avoid_for": ["legal.audit"],
"palette": {"background": "#F2EFE6", "text": "#2F4224", "muted": "#66914C", "accent": "#DA9EB7", "support": ["#FFFFFF"]},
"shape_language": {"panel_treatment": "court-like lanes and team panels", "corner_radius": "medium", "border_weight": "medium", "texture": "court lane geometry"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "high", "node_budget": {"source_nodes": 61, "source_text": 37, "source_shapes": 11, "source_connectors": 13}},
"slide_translation": {"recommended_layouts": ["process", "timeline", "loop"], "svglide_primitives": ["connector_flow", "path", "geometric_shape"], "fallback_policy": "Use lanes and flows to encode motion; keep sports metaphor subtle."},
"quality_oracle": {"expected_style_signals": ["court lanes", "green team palette", "pink accent"], "warning_thresholds": {"text_boxes_max": 25, "accent_colors_max": 3}}
},
{
"style_id": "crayon_stack",
"display_name": "Crayon Stack",
"group": "Bold",
"source_token": "JjKIwig1vhnXq7bbeCfutuKFseh",
"formality": "low",
"vibe": ["creative", "playful", "stacked"],
"best_for": ["cover.hero", "icon_capability_map", "brand_system"],
"avoid_for": ["executive.audit"],
"palette": {"background": "#FFFFFF", "text": "#222222", "muted": "#8A2E43", "accent": "#FF472B", "support": ["#D3FE79", "#FBB8FD", "#2A8F6D", "#7E90FC"]},
"shape_language": {"panel_treatment": "stacked crayon color bars", "corner_radius": "medium", "border_weight": "heavy", "texture": "handmade colorful blocks"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "low", "node_budget": {"source_nodes": 51, "source_text": 37, "source_shapes": 8, "source_connectors": 3}},
"slide_translation": {"recommended_layouts": ["cover", "capability_map", "brand"], "svglide_primitives": ["geometric_shape", "icon", "typography"], "fallback_policy": "Use colorful stacks for categories; avoid making every label a different color."},
"quality_oracle": {"expected_style_signals": ["crayon color stack", "playful strong accents", "bold category blocks"], "warning_thresholds": {"text_boxes_max": 22, "accent_colors_max": 6}}
},
{
"style_id": "grove_block",
"display_name": "Grove Block",
"group": "Bold",
"source_token": "Mq2XwYKDEhbnRmbIxAfuZGCQsOb",
"formality": "medium",
"vibe": ["ecosystem", "bold", "result"],
"best_for": ["kpi.big-number", "metaphor_loop", "process.flow"],
"avoid_for": ["quiet.editorial"],
"palette": {"background": "#FCF6F1", "text": "#01623F", "muted": "#008248", "accent": "#FCC715", "support": ["#F7F1EC", "#F6BDDA"]},
"shape_language": {"panel_treatment": "bold green blocks with yellow emphasis", "corner_radius": "medium", "border_weight": "medium", "texture": "organic block system"},
"density": {"text_density": "medium", "label_density": "high", "connector_density": "medium", "node_budget": {"source_nodes": 73, "source_text": 44, "source_shapes": 20, "source_connectors": 9}},
"slide_translation": {"recommended_layouts": ["kpi", "loop", "process"], "svglide_primitives": ["geometric_shape", "connector_flow", "typography"], "fallback_policy": "Use green blocks for system stages and yellow for outcome emphasis."},
"quality_oracle": {"expected_style_signals": ["bold green blocks", "yellow result accent", "ecosystem grouping"], "warning_thresholds": {"text_boxes_max": 28, "accent_colors_max": 3}}
},
{
"style_id": "mint_brut",
"display_name": "Mint Brut",
"group": "Bold",
"source_token": "BMe4wBmwlhGfNCbooCvuShTfs4v",
"formality": "low",
"vibe": ["brutalist", "technical", "mint"],
"best_for": ["architecture.layered", "process.flow", "cover.hero"],
"avoid_for": ["quiet.training"],
"palette": {"background": "#FFFBF3", "text": "#000000", "muted": "#D0FDE4", "accent": "#70F0A8", "support": ["#F888C8", "#F0DE4E"]},
"shape_language": {"panel_treatment": "brutalist mint panels with loud highlights", "corner_radius": "low", "border_weight": "heavy", "texture": "brutalist blocks"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "high", "node_budget": {"source_nodes": 75, "source_text": 38, "source_shapes": 23, "source_connectors": 11}},
"slide_translation": {"recommended_layouts": ["architecture", "process", "cover"], "svglide_primitives": ["geometric_shape", "connector_flow", "typography"], "fallback_policy": "Use heavy frames and mint surfaces; keep text large and sparse."},
"quality_oracle": {"expected_style_signals": ["mint brutalist panels", "black outlines", "pink/yellow highlights"], "warning_thresholds": {"text_boxes_max": 25, "accent_colors_max": 4}}
},
{
"style_id": "neo_grid_bold",
"display_name": "Neo-Grid Bold",
"group": "Bold",
"source_token": "WwstwfAj1hIZXhbDwdDuwdpDsBh",
"formality": "medium",
"vibe": ["neo-grid", "technical", "high-contrast"],
"best_for": ["technical_texture", "architecture.layered", "fake_ui_dashboard"],
"avoid_for": ["paper.explainer"],
"palette": {"background": "#F5F4EF", "text": "#0A0A0A", "muted": "#8A8A85", "accent": "#E6FF3D", "support": ["#0A0A0A"]},
"shape_language": {"panel_treatment": "black grid rails with neon highlight", "corner_radius": "low", "border_weight": "heavy", "texture": "explicit neo grid"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "high", "node_budget": {"source_nodes": 69, "source_text": 40, "source_shapes": 12, "source_connectors": 17}},
"slide_translation": {"recommended_layouts": ["technical_texture", "architecture", "dashboard"], "svglide_primitives": ["texture", "connector_flow", "geometric_shape"], "fallback_policy": "Use explicit grid lines/dots; do not rely on SVG pattern."},
"quality_oracle": {"expected_style_signals": ["black grid structure", "neon lime focus", "technical rails"], "warning_thresholds": {"text_boxes_max": 25, "accent_colors_max": 2}}
},
{
"style_id": "riso_brut",
"display_name": "Riso Brut",
"group": "Bold",
"source_token": "Mztnwj2ouhot6VbtbwMuWN4SsRb",
"formality": "low",
"vibe": ["riso", "poster", "multicolor"],
"best_for": ["brand_system", "cover.hero", "image.story"],
"avoid_for": ["legal.audit"],
"palette": {"background": "#EFE9D9", "text": "#0F0F0F", "muted": "#136636", "accent": "#F5C518", "support": ["#1F8A4C", "#E85A1F", "#F06CA8", "#D14E8B"]},
"shape_language": {"panel_treatment": "riso-print blocks and overlapping color plates", "corner_radius": "low", "border_weight": "medium", "texture": "riso poster plates"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "low", "node_budget": {"source_nodes": 71, "source_text": 38, "source_shapes": 29, "source_connectors": 4}},
"slide_translation": {"recommended_layouts": ["brand", "cover", "image"], "svglide_primitives": ["geometric_shape", "typography", "image_overlay"], "fallback_policy": "Use flat color plates; avoid opacity-heavy overlaps unless preflight accepts them."},
"quality_oracle": {"expected_style_signals": ["riso color plates", "poster energy", "cream paper"], "warning_thresholds": {"text_boxes_max": 24, "accent_colors_max": 6}}
},
{
"style_id": "specimen_bold",
"display_name": "Specimen Bold",
"group": "Bold",
"source_token": "BNFmwKX6OhWRfbb3SX4uRoHesD6",
"formality": "medium",
"vibe": ["specimen", "concept", "modular"],
"best_for": ["icon_capability_map", "comparison.two-column", "section.divider"],
"avoid_for": ["paper.explainer"],
"palette": {"background": "#F3F3F3", "text": "#2E302E", "muted": "#30A1E5", "accent": "#3EC06A", "support": ["#FFFFFF", "#FBEF4A"]},
"shape_language": {"panel_treatment": "specimen cards with multiple focus markers", "corner_radius": "medium", "border_weight": "medium", "texture": "sample-card layout"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "medium", "node_budget": {"source_nodes": 57, "source_text": 37, "source_shapes": 8, "source_connectors": 12}},
"slide_translation": {"recommended_layouts": ["capability_map", "comparison", "section"], "svglide_primitives": ["icon", "geometric_shape", "connector_flow"], "fallback_policy": "Use specimen cards for concepts and blue/green markers for relationships."},
"quality_oracle": {"expected_style_signals": ["specimen cards", "blue/green markers", "modular concept blocks"], "warning_thresholds": {"text_boxes_max": 24, "accent_colors_max": 4}}
},
{
"style_id": "stencil_tablet",
"display_name": "Stencil & Tablet",
"group": "Bold",
"source_token": "J1BKw4SxhhC3kTbD77auLJ5UsRc",
"formality": "medium",
"vibe": ["stencil", "method", "action"],
"best_for": ["quote.insight", "process.flow", "agenda.structured"],
"avoid_for": ["quiet.editorial"],
"palette": {"background": "#F4EFE0", "text": "#0A0A0A", "muted": "#E2DCC9", "accent": "#D8A93B", "support": ["#EE7A2E", "#C73B7A", "#2D7E73"]},
"shape_language": {"panel_treatment": "stencil panels and tablet-like blocks", "corner_radius": "low", "border_weight": "medium", "texture": "stencil print panels"},
"density": {"text_density": "medium", "label_density": "medium", "connector_density": "low", "node_budget": {"source_nodes": 51, "source_text": 39, "source_shapes": 7, "source_connectors": 5}},
"slide_translation": {"recommended_layouts": ["quote", "process", "agenda"], "svglide_primitives": ["typography", "geometric_shape", "annotation"], "fallback_policy": "Use stencil panels for method/action emphasis; keep decorative colors role-bound."},
"quality_oracle": {"expected_style_signals": ["stencil panels", "tablet blocks", "gold/orange/magenta accents"], "warning_thresholds": {"text_boxes_max": 24, "accent_colors_max": 5}}
}
]
}

View File

@@ -0,0 +1,89 @@
# SVGlide Style Presets
`style-presets.json` is the runtime source of truth for the 35 `beautiful-feishu-whiteboard` style presets. This Markdown file is only a human-readable guide.
## Boundary
Style presets are not slide templates. They do not replace `visual_recipe`, `renderer_id`, or the page semantic plan.
- `visual_recipe`: explains the page structure and SVG-native value, such as `path_flow`, `technical_texture`, or `fake_ui_dashboard`.
- `style_preset`: selects the visual language, palette, panel treatment, connector density, label density, and texture.
- `style_system`: records how the selected preset is translated into the current deck.
Do not copy raw whiteboard nodes, raw coordinates, source prompts, source file paths, tool names, source tokens, or preset names into visible slide content.
## Required Plan Fields
For `output_mode="svglide-svg"`, the deck plan must include:
```json
{
"style_preset": "raw_grid",
"style_selection_reason": "raw_grid fits technical training pages that need dense but readable visual structure",
"style_system": {
"palette": {
"background": "#F5F5F5",
"text": "#0A0A0A",
"accent": "#F2D4CF"
},
"typography": "strong title, readable native text labels",
"background_strategy": "muted grid panels with one stable background family",
"motif": "dense grid panels with restrained accent labels"
}
}
```
Each slide must also include:
```json
{
"visual_recipe": "path_flow",
"visual_signature": "curved route path with explicit stage annotations",
"svg_effects": ["path", "connector_flow", "typography"],
"svg_primitives": ["path", "annotation"],
"required_primitives": ["path", "annotation"]
}
```
Use `visual_plan` as a nested container when useful. `svg_preflight.py` accepts both the nested shape and the existing flat fields; nested `visual_plan` wins when both are present.
## Selection Rule
1. Choose intensity first.
- `Restrained`: serious, quiet, institutional, text-first decks.
- `Balanced`: default for business, technical, training, and explanatory decks.
- `Bold`: posters, showcases, event material, playful explainers, high-energy pages.
2. Match the user's tone and topic.
3. Keep the semantic plan stable. Switching from `raw_grid` to `reading_room` should change visual treatment, not invent new facts or rearrange the story.
4. Pick page-level overrides only for cover, section divider, or poster-like moments. Most slides should inherit the deck-level `style_preset`.
## SVGlide-Safe Translation
Translate style into supported SVG primitives:
- Palette -> explicit `fill`, `stroke`, and text colors.
- Panel treatment -> `rect`, `path`, and grouped layout boxes.
- Connector density -> explicit `line` or supported `path`; do not rely on `marker` or key-path `stroke-dasharray`.
- Texture -> repeated native `line`, `circle`, or `rect`; do not rely on `<pattern>` as the only effect.
- Image overlay -> real `<image slide:role="image">` plus explicit shape masks/overlays when needed.
Unsafe effects such as `filter`, `mask_clip`, `pattern`, `symbol`, `stroke_dasharray`, and `image_opacity` may appear in the plan only when a safe rewrite or fallback is declared.
## Quality Gates
Before calling `slides +create-svg`, run:
```bash
python3 skills/lark-slides/scripts/svg_preflight.py \
--plan .lark-slides/plan/<deck-id>/slide_plan.json \
--input .lark-slides/plan/<deck-id>/pages/page-001.svg
```
The preflight checks:
- preset exists in `style-presets.json`;
- `style_system` has palette, typography, background strategy, and motif;
- each page declares `visual_signature` and `svg_effects`;
- unsafe effects have fallback or rewrite notes;
- declared effects and primitives are present in the SVG source;
- visible slide text does not leak preset names, source tokens, prompts, tool names, or local file paths.

View File

@@ -0,0 +1,107 @@
# SVGlide Aesthetic Review
Use this file after generating local SVG/HTML preview and before calling
`slides +create-svg`. It is the short execution checklist distilled from:
`/Users/bytedance/bd-projects/workspaces/SVGlide/svglide-visual-guidance/svg_aesthetic_rubric.md`.
This review complements `svg_preflight.py`. Preflight catches deterministic
protocol, plan, and bbox problems; this checklist catches rendered visual
quality issues that need human or screenshot-based judgment.
## Required Review Flow
1. Generate local SVG files and, when possible, a local `preview.html`.
2. Run `svg_preflight.py --plan ... --input ...`; fix all errors first.
3. Open or inspect the preview. Review all slides, not just the cover.
4. Repair repeated layout problems in the generator or source SVG, not by
changing only `slide_plan.json`.
5. Re-run preflight and preview before live creation.
Do not use preview review as a substitute for live readback. Service conversion
can still change text boxes, image tokens, path bounds, and unsupported effects.
## Blocking Visual Issues
Fix these before calling live API:
| Issue | Action |
|---|---|
| Text overlap, text container overflow, or clipped headline | Regenerate layout boxes or reduce text; do not just shrink everything |
| Badge, pill, section tag, or page label touches/overlaps headline | Move badge outside title block or add at least 12-16px vertical gap |
| Decorative line or band presses against title | Move the line above the title zone or lower title; keep clear breathing room |
| Main content outside `960 x 540` or safe area | Recompute coordinates using the 960x540 canvas |
| Low contrast text over light image/background | Add solid backing, overlay, or switch text color |
| Empty image frame or broken preview image | Replace asset or use a visual fallback before live creation |
| Page lacks focal point | Rebuild the page around one dominant number, diagram, image, route, or title |
| Page is ordinary cards/bullets with no SVG advantage | Choose a better `visual_recipe` or switch away from SVG route |
| Same layout issue repeats across multiple slides | Fix the shared generator rule, then regenerate affected pages |
## Issue Severity
Use these severities in preview notes and final validation records:
| Severity | Meaning | Action |
|---|---|---|
| P0 | The deck should not be created live | Fix or regenerate before `slides +create-svg` |
| P1 | The deck can render, but user-facing quality is clearly below target | Repair before delivery; only continue as draft if explicitly accepted |
| P2 | Minor polish or residual risk | Record and fix when time allows |
Default mapping:
- P0: preflight error, unsafe SVG, broken/empty image, canvas crop, clipped or overlapping essential text, unreadable contrast, missing required asset, no fallback for unsupported visual.
- P1: weak focal point, repeated layout skeleton, decorative/title crowding, low visual hierarchy, chart/diagram intent mismatch, visible SVG advantage weak.
- P2: minor alignment variance, small color inconsistency, non-critical source metadata warning, polish-only spacing issue.
## Scoring Rubric
Use a 0-100 review score. The default target for user-facing decks is `>= 75`.
Below `65`, regenerate or repair before live creation.
| Dimension | Weight | Good result |
|---|---:|---|
| Communication fit | 15 | Page type and visual form match the user's intent |
| Visual hierarchy | 15 | One focal point is clear within two seconds |
| Layout stability | 15 | Grid, spacing, alignment, and safe area are consistent |
| Readability | 15 | Font size, line length, contrast, and wrapping are readable |
| Color discipline | 10 | Accent colors are few and semantically consistent |
| Data/diagram integrity | 10 | Charts, flows, and diagrams express relationships honestly |
| Style consistency | 8 | Icons, radii, strokes, shadows, and motif feel like one deck |
| SVG advantage | 7 | The page visibly benefits from path, texture, chart geometry, flow, or overlay |
| Source/asset traceability | 5 | External references and preview assets are recorded when used |
## Review Questions
Ask these for each page:
- What is the one-sentence takeaway?
- Where does the eye land first, and is that the intended `visual_focal_point`?
- Does the scan path follow title -> focal visual -> evidence -> detail?
- Are any badges, lines, watermarks, labels, or thumbnails crowding text?
- Is the page using SVG-native structure, or only ordinary boxes and text?
- If this page were converted to a plain XML/PPT card layout, what would be lost?
- Are chart/flow/table choices appropriate for the relationship being shown?
- Are colors and emphasis consistent with the rest of the deck?
## Repair Priority
1. Layout correctness: canvas, safe area, overlap, overflow, clipping.
2. Readability: contrast, font size, line length, enough text box height.
3. Hierarchy: one focal object, clear title, supporting details demoted.
4. SVG advantage: path/flow/chart/icon/texture/image overlay actually present.
5. Deck rhythm: avoid repeating the same skeleton with only copy changed.
6. Asset/source hygiene: preview assets are visible and source metadata exists.
## Accepted Output Note
When reporting validation, say exactly what was checked:
```text
SVG preview review:
- preflight: passed / fixed errors first
- preview_path: .lark-slides/plan/<deck-id>/preview.html
- preview: checked all N pages for overlap, safe area, readability, and repeated layout issues
- visual_score: 82 / threshold 75
- issue_ids: none / [P1 visual.layout.decorative_line_title_pressure page=3]
- action: create_live / repair_and_rerun / draft_only
- remaining risk: live readback may still change text bbox or unsupported effects
```

View File

@@ -7,6 +7,7 @@
xmlns="http://www.w3.org/2000/svg"
xmlns:slide="https://slides.bytedance.com/ns"
slide:role="slide"
slide:contract-version="svglide-authoring-contract/v1"
width="960"
height="540"
viewBox="0 0 960 540"
@@ -24,6 +25,7 @@
- root 必须是非 namespaced 的 `<svg>`,不能是 `<svg:svg>`
- root 必须声明 `xmlns:slide="https://slides.bytedance.com/ns"`
- root 必须包含 `slide:role="slide"`
- root 应包含 `slide:contract-version="svglide-authoring-contract/v1"`,用于标识这是 SVGlide authoring contract 输入,而不是普通 SVG。
- 可渲染元素必须有对应 `slide:role`shape 使用 `slide:role="shape"`,图片使用 `slide:role="image"`
- `<g>` 和嵌套 `<svg>` 可以作为容器,用于继承样式和 transform容器内真正渲染的元素仍必须声明 `slide:role`
- `slide:role="shape"` 目前只支持 `rect``ellipse``circle``line``path``foreignObject`
@@ -74,8 +76,36 @@ CLI 会把这些几何属性作为生成质量门禁:值只能是数字或 `px
- Path: 只使用 `M/L/H/V/C/Q/Z`CLI 会拒绝 arc `A`、smooth curve `S/T` 和其他未知命令。
- Text: `foreignObject slide:shape-type="text"` 内支持常见 XHTML 文本标签、`br` 和基础文字样式。
## SVG-native 效果的 SVGlide-safe 写法
视觉参考图、浏览器 SVG demo 或 `svglide-visual-effects-gallery.html` 只能作为效果方向,不能直接当作 `slides +create-svg` 输入。生成器必须把浏览器 SVG 能力改写为当前 SVGlide 支持面:
| 浏览器 SVG 常见写法 | SVGlide-safe 写法 |
|---|---|
| 根级 `<text>` / 普通 SVG text | `foreignObject slide:role="shape" slide:shape-type="text"`,并显式写 `font-size``font-weight``color``line-height` |
| `<polygon>` / `<polyline>` | 改成 `path slide:role="shape"`,只使用 `M/L/H/V/C/Q/Z` |
| `<marker>` 箭头 | 用独立三角形 `path` 或短 line + arrowhead path 显式绘制 |
| `<pattern>` 网格、点阵、纹理 | 用重复的 `line``circle``rect` 显式铺排;不要依赖 pattern 展开 |
| `mask` / `clipPath` 大字裁切 | 用大字描边、深色/渐变背板、半透明 shape overlay 或裁切后的本地图片替代 |
| 多层 `filter`、blur、glow | 用多层半透明 circle/rect/path 模拟光晕;仅把简单 drop-shadow 当增强,不当核心表达 |
| `stroke-dasharray` 关键路线 | 用短 line segment 或 filled dot markers 手工排布;关键流程不要只靠虚线;带 `route` / `path` / `flow` / `loop` / `timeline` / `rail` 等语义的虚线会被 preflight 视为错误 |
| `<image opacity="...">` | MVP preflight 只 warning高保真场景应预合成到图片或在图片上方加半透明 `rect slide:role="shape"` |
| iconfont / 外链 SVG 图标 | 用 SVGlide-safe path/line/rect/circle 组合本地绘制,或先转成受支持的本地图片资产 |
每个 SVG 页面应通过 `visual_recipe` 证明自己值得使用 SVG要么有强视觉主标题要么有路径/流向/隐喻/标注/图标系统/微图表/纹理/仪表盘等 SVG-native 结构。只有 `rect + foreignObject` 的普通卡片页应优先走 XML/SXSD。
文本样式应使用 parser 友好的显式 CSS 属性,例如 `font-size``font-weight``font-family``color``line-height``text-align``letter-spacing`。不要依赖 `font:` shorthand、复杂 flex 布局或浏览器默认样式来表达关键字号、加粗和行距;这些在转换到 SXSD/XML 时可能降级为默认样式。
白色或接近白色的文字必须完整落在深色 shape 承载底上;如果标题跨到浅色图片、白色蒙层或白底,生成器应扩大深色底、加背板/遮罩,或改用深色文字。圆形/椭圆节点内只放短标签,解释句、指标和说明放到独立 callout、legend 或机制表中。
生成 live smoke 或跨 lane 验证用 SVG 时,颜色优先写成 hex/rgb 加独立透明度属性,例如 `fill="#0F172A" opacity="0.72"``stroke="#38BDF8" stroke-opacity="0.8"`。不要在首轮验证里大量依赖 `rgba(...)` 作为 SVG leaf 的 `fill` / `stroke` 值;不同 server lane 的 paint 解析能力可能不一致hex + opacity 更容易定位问题。渐变仍按 XML 协议要求使用 `rgba(...)` 停靠点。
图片透明度当前不是稳定协议面:`<image opacity="...">` 在 SVG 输入中会通过 CLI 传给服务端,但转换后的 Slides XML `<img>` 不一定保留 alpha。MVP 阶段 preflight 只 warning生成器不得在高保真页面依赖 image opacity要么把淡化效果预合成到本地图片文件要么用一个半透明 `rect slide:role="shape"` 覆盖在图片上方。shape opacity 会转换为 Slides XML `alpha`,比 image opacity 更稳定。
圆形和椭圆描边宽度也不是稳定协议面:`circle` / `ellipse``stroke-width` 可能在 readback 中降级。关键圆环请用两层填充圆/椭圆模拟,或改用 path/rect普通细描边可以保留但需要视觉回读确认。
虚线描边也不是稳定协议面:`stroke-dasharray`,尤其是自定义 path 上的虚线闭环,可能在 readback 中降级。关键流程线、路线图和闭环图用短 line segment 或 filled dot markers 显式绘制;带 `route``path``flow``loop``timeline``rail` 等语义的 dashed path 会被 `svg_preflight.py` 作为 error 拦截。普通装饰虚线也需要 live readback 复核。
## 不支持
- 不要把普通 SVG 直接交给 `+create-svg`CLI 不会自动补齐 SVGlide 协议。
@@ -83,16 +113,41 @@ CLI 会把这些几何属性作为生成质量门禁:值只能是数字或 `px
- 不要把 `<g>` 当作可渲染 shape`<g>` 只是容器,实际 `rect``path``foreignObject``image` 等子元素仍需各自声明 `slide:role`
- 不支持根级 `<text slide:role="text">`;用 `foreignObject + slide:shape-type="text"`
- 不要在 `<image>` 上保留 `xlink:href`CLI 会统一输出 canonical `href`
- 不要用 http(s) 或 data URL 外链图片;先下载到本地并让 CLI 上传,或用 `--assets` 提供已上传 file token。
- Preview/MVP 阶段允许 http(s) 或 data URL 图片通过 preflight warning用于快速验证丰富视觉live 转换和 readback 可见性不保证,必须回读确认。正式交付优先下载到本地并让 CLI 上传,或用 `--assets` 提供已上传 file token。
- `slides +create-svg` MVP 不支持指定 `beforeSlideBlockID` 插入到某一页前;它创建新 presentation 后按 `--file` 顺序追加。
这些能力依赖 slide server SVGlide parser 新版本。如果 BOE/线上未部署对应 server 分支CLI 放行后仍可能收到服务端 `SVGLIDE_ERROR_JSON` 或 generic invalid param。
## 图片与 Metadata
SVG deck 默认应使用真实图片资产,不要为了规避上传链路而全程用纯矢量 shape 冒充配图。宣传、产品、品牌、案例和视觉展示型 deck 至少应包含封面/半出血主视觉/案例场景/产品截图等图片使用;只有用户明确要求纯矢量,或图片获取、上传链路不可用时,才退回纯矢量方案,并在结果中说明原因。
SVG deck 默认应使用真实图片资产,不要为了规避上传链路而全程用纯矢量 shape 冒充配图。Preview 阶段图片是拉开 SVGlide 和 XML 生成差距的关键能力:宣传、产品、品牌、案例、教学和视觉展示型 deck 应优先根据用户 query、deck 主题和页面标题去网络检索并拉取强相关图片,再包含封面/半出血主视觉/案例场景/产品截图/材质纹理/图鉴图等图片使用;只有用户明确要求纯矢量,或图片获取、上传链路完全不可用时,才退回纯矢量方案,并在结果中说明原因。
图片资产还必须规避版权风险:只使用用户提供、公司/项目自有、明确可商用授权图库,或授权条件清晰的 AI 生成资产。推荐来源包括 Unsplash、Pexels、Pixabay、Openverse、Wikimedia Commons、The Met Open Access、Smithsonian Open Access 和 NASA Image and Video Library但每张图都必须检查具体 license。不要使用版权状态不明的搜索图片、新闻配图、第三方 logo、竞品官网截图或素材站预览图。生成 deck 时应在素材清单或 README 中记录图片来源、授权/许可类型、下载 URL 或生成方式、是否需要署名;无法确认授权时不得使用该图片。
图片资产采用双模式:
- **Preview mode**:版权/授权不是阻断项。SHOULD 先从用户 query、deck 标题、章节标题和 page takeaway 生成图片检索词,去网络检索并拉取主题强相关图片;也可以使用公开可访问图片 URL、搜索图片、新闻/历史/艺术/科普图片、官网截图、产品截图、网页截图、材质纹理或 AI 生成图作为视觉占位。必须记录 `retrieval_query``source_url` 或生成方式,并把 `license` 写成 `preview_unverified`。不要使用明显不适当素材、敏感肖像,或会造成商业背书误导的 logo/商标。
- **Production mode**:正式交付必须替换为用户提供、公司/项目自有、明确可商用授权图库,或授权条件清晰的 AI 生成资产。推荐来源包括 Unsplash、Pexels、Pixabay、Openverse、Wikimedia Commons、The Met Open Access、Smithsonian Open Access 和 NASA Image and Video Library但每张图都应检查具体 license、署名和第三方权利。
MVP 阶段素材清单不完整只作为 warning但生成 deck 时仍应在素材清单或 README 中记录图片检索词、图片来源、授权/许可类型、下载 URL 或生成方式、是否需要署名;无法确认授权时应显式标记风险并在正式交付前替换。
当 SVG source 使用 `<image>` 时,对应 slide plan 应尽量有 `asset_contract`,并至少包含:
```json
{
"mode": "preview",
"source_type": "public_url | web_search_preview | screenshot | Unsplash | Pexels | procedural | ai_generated | user_provided | owned",
"retrieval_query": "topic-specific image query derived from user query and page topic",
"license": "preview_unverified",
"local_path": "@./assets/hero.jpg",
"href": "https://example.com/hero.jpg",
"usage_page": 1,
"source_url": "https://...",
"retrieved_at": "2026-06-08",
"generated_by": "optional when source_type is procedural/ai_generated",
"replacement_required": true
}
```
无图片页可以写 `"asset_contract": "none_required"`。如果 SVG source 检测到 image primitive`asset_contract` 缺少检索词、来源、许可、本地路径或使用页MVP 阶段 preflight 只 warningpreview 中可用 `license=preview_unverified` 明确标记,正式交付仍应补齐或替换为来源清晰的图片资产。
`slides +create-svg` 会把 `<image href="@./image.png">` 上传为 file token并注入

View File

@@ -0,0 +1,106 @@
# SVGlide Visual Recipes
This file is the short executable recipe guide for `slides +create-svg`.
It distills the research catalog into generation-time rules that can fit into
the agent context. The longer research source remains outside the CLI skill:
`/Users/bytedance/bd-projects/workspaces/SVGlide/svglide-visual-guidance/visual_recipe_catalog.md`.
## Boundary
- `visual_recipe` defines page structure and why this page deserves SVG.
- `style_preset` defines visual language, palette, texture, density, and motif.
- `renderer_id` defines the concrete geometry renderer.
Do not use `style_preset` as a substitute for `visual_recipe`. Do not invent
new recipe ids in `slide_plan.json`.
## Hard Defaults
- Canvas: `width="960" height="540" viewBox="0 0 960 540"`.
- Safe area: keep key text, labels, charts, cards, nodes, and legends inside
`x=48..912` and `y=40..500`.
- Grid: use a stable 12-column or 8px-step layout; avoid ad hoc coordinates.
- Text: Chinese body copy should stay around 28 characters per line; English
body copy around 62 characters per line.
- Decoration: decorative lines, watermarks, texture, and background geometry
must not compete with or touch the title/focal content.
- Deck diversity: 8+ page SVG decks should use at least 5 visual recipe families.
## Plan Fields
Every SVG page plan must include these fields before writing SVG:
```json
{
"visual_recipe": "path_flow",
"visual_intent": "show a staged route from current state to target state",
"visual_focal_point": "curved route spine with the final target node",
"visual_signature": "curved route path plus stage annotations",
"svg_effects": ["path", "connector_flow", "typography"],
"required_primitives": ["path", "annotation"],
"svg_primitives": ["path", "annotation", "typography"],
"xml_like_risk": "without the route geometry this becomes ordinary bullets",
"content_density_contract": "flow >= 4 stages",
"risk_flags": [],
"source_policy": "do not invent unsupported numbers"
}
```
## Recipe Selection Matrix
Use these CLI-supported underscore ids in `slide_plan.json`.
| User intent | `visual_recipe` | Must show in SVG source |
|---|---|---|
| Cover, section opener, hero statement | `hero_typography` | large type, geometric carrier, clear focal object |
| Strategic framework, strong geometric layout | `geometric_composition` | non-card geometry, `path` or shaped regions |
| Roadmap, journey, process, route | `path_flow` | explicit path/line spine, arrows or stage markers |
| KPI, scorecard, data recap | `infographic_scorecard` | big number plus micro chart or gauge geometry |
| Capability map, module overview | `icon_capability_map` | consistent SVG-safe icons and labeled regions |
| Depth, atmosphere, concept emphasis | `gradient_depth` | gradient or layered translucent geometry plus readable text |
| Product/result/image story | `mask_clip_showcase` | image region plus safe overlay/crop simulation |
| Technical system, grid, coded texture | `technical_texture` | repeated lines/dots/rects, grid, scanline, or diagram texture |
| Loop, flywheel, feedback system | `metaphor_loop` | closed path or looped process plus input/output labels |
| Diagnosis, callout, focused annotation | `spotlight_annotation` | highlight region, callout line, annotation target |
| Dashboard, console, monitoring surface | `fake_ui_dashboard` | UI frame, status bar, metrics, micro charts/log rows |
| Brand or series identity page | `brand_system` | stable title system, motif, palette, and repeated identity element |
## Safe Effects
Prefer effects that can be represented by SVGlide-safe primitives:
- `path`: curves, waves, routes, custom shapes.
- `gradient`: background depth and emphasis; keep text on solid backing.
- `texture`: repeated `line`, `circle`, or `rect`; do not rely on `<pattern>`.
- `connector_flow`: explicit line/path plus arrow triangles or dots.
- `chart_geometry`: bars, points, lines, gauges, axes, and labels.
- `grid_geometry`: matrix, table-like visual summary, structured alignment grid.
- `watermark_text`: low-contrast large text that never blocks reading.
- `image_overlay`: real image plus explicit translucent shape overlays.
- `spotlight`: layered translucent shapes, not complex filter-only glow.
## Risky Effects
These may appear in visual planning only when a safe rewrite or fallback is
declared in `risk_flags` / `recipe_fallback`:
- `filter`
- `mask_clip`
- `pattern`
- `symbol`
- `stroke_dasharray`
- `image_opacity`
For critical visuals, rewrite them into explicit shapes, lines, dots, overlays,
or pre-composited images before calling `slides +create-svg`.
## Anti-Regression Rules
- A page that is mostly `rect + foreignObject` is not enough for SVGlide unless
it also has a real SVG-native structure: path, chart geometry, icon system,
texture, spotlight, dashboard frame, connector flow, or image overlay.
- The first visible object should match the page's `visual_focal_point`.
- Similar pages may share `style_preset`, but should not share the same layout
skeleton with only text and background color changed.
- Dotted recipe names from research notes, such as `cover.hero`, are not valid
runtime ids. Map them to the underscore ids above before writing the plan.

View File

@@ -44,6 +44,56 @@ python3 skills/lark-slides/scripts/xml_text_overlap_lint.py --input <presentatio
| `xml_not_well_formed` | XML 语法错误或文本未转义 | 修复标签闭合、属性引号、`&` / `<` / `>` 转义 |
| `bbox_overlap` | 文本元素的估算绘制区域明显重叠 | 拉开文本坐标、缩小文本框/字号,或改成明确的分栏/分组结构 |
## Automated SVGlide Plan And SVG Preflight
`slides +create-svg` 前,必须先运行 SVG plan/source preflight
```bash
python3 skills/lark-slides/scripts/svg_preflight.py \
--plan .lark-slides/plan/<deck-id>/slide_plan.json \
--input .lark-slides/plan/<deck-id>/pages/page-001.svg
```
通过标准:
- `summary.error_count == 0`,任何 error 都必须先修复再调用 live API。
- `style_preset` 必须存在于 `references/style-presets.json`
- `style_selection_reason` 必须说明为什么这个 preset 适合当前 deck。
- `style_system` 必须包含 palette、typography、background strategy 和 motif。
- 每页必须包含 `visual_recipe``visual_signature``svg_effects``required_primitives``svg_primitives``xml_like_risk``content_density_contract``risk_flags``source_policy`
- declared `svg_effects``required_primitives` 必须能在对应 SVG source 中命中。
- 可见 slide 文本不得泄漏 preset 名称、source token、prompt、tool name 或本地文件路径。
常见 code 的处理方向:
| code | 含义 | 处理方式 |
|------|------|----------|
| `plan_style_preset_unknown` | plan 引用了不存在的 35 preset | 从 `style-presets.json` 选择有效 `style_id` |
| `plan_missing_visual_signature` | 页面没有声明 SVG 视觉记忆点 | 写清这页相对普通 PPT/XML 模板的独特视觉结构 |
| `plan_missing_svg_effects` | 页面没有声明 SVG 表达能力 | 声明真实会绘制的 `path``connector_flow``gradient``texture``chart_geometry` 等 |
| `plan_svg_effect_not_found` | plan 声明的 effect 没在 SVG source 中出现 | 修改 SVG source或删除不真实的 effect 声明 |
| `plan_style_preset_visible_leak` | 可见文本泄漏 preset 名/source token | 仅在 plan metadata 中保留 preset 信息,画面只写用户主题内容 |
## SVGlide Aesthetic Preview Review
`svg_preflight.py` 通过后,走 `slides +create-svg` 前还必须做本地预览审查。读取 [svg-aesthetic-review.md](svg-aesthetic-review.md),检查 rendered preview而不是只看 plan 字段或静态 XML。
通过标准:
- 所有页面都检查过,不只检查封面。
- 无标题、正文、badge、装饰线、图片框、图表标签的明显重叠或裁切。
- root 和主要内容遵循 `960 x 540` 画布和 safe area。
- 每页有清晰 `visual_focal_point`,视觉焦点对应 `visual_signature`
- 页面不是普通卡片/bullet 页伪装成 SVG应能看出 path、texture、chart geometry、connector flow、image overlay、icon system、dashboard frame 或其他 SVG-native 结构。
- 多页没有重复出现同一个布局错误;如果有,必须修生成规则并重新生成相关页面。
- 用户可见交付 deck 的审美目标默认不低于 `75/100`;低于 `65/100` 应重新生成或显式降级为草稿。
- 验证记录包含 `preview_path``visual_score``threshold``issue_ids``action``action=create_live` 才能继续调用 live API`action=repair_and_rerun` 必须先修 source SVG / plan 并重新跑 preflight。
这一步和 preflight 分工如下:
- `svg_preflight.py`: 负责协议、plan、枚举、必填字段、bbox、primitive 命中和确定性错误。
- `svg-aesthetic-review.md`: 负责截图/预览视角的层级、节奏、压迫感、重复问题、可读性和 SVG 视觉优势。
## Page Count And Structure
- 实际页数必须等于用户要求或 `slide_plan.json` 的页数。

View File

@@ -20,6 +20,16 @@
- Keep backgrounds consistent with the deck's `visual_system.background_strategy`. Normal content pages should use the same base background unless there is a clear page-role reason to change.
- Treat text fit as a layout constraint, not a cleanup step. If a text box is too small for the intended line count, shorten the text, split it, or allocate more space before creating XML.
## Title Zone Guardrails
The title zone is the most common place for subtle overlap. Treat badges, decorative rules, headlines, and the first content row as one unit.
- If a page uses a chapter badge, status pill, or small label above the headline, the headline text top must be at least `8` px below the badge bottom; prefer `12` px when the headline is bold or larger than `28` px.
- If a decorative horizontal line, accent rule, or divider sits above a headline, the line bottom must be at least `16` px above the headline text top; prefer `20-28` px when the headline is larger than `48` px.
- When a headline is moved down to create breathing room, move the subtitle, column headers, and main content start down together. Do not fix one collision by creating a new one below.
- Do not place large headlines directly under a top border or accent stripe. The decoration should frame the title, not press on it.
- Check the same page family across the whole deck. If one section/title page has a badge-headline collision, scan all pages with the same badge pattern before accepting the deck.
## Background And Motif Consistency
Decks can vary page backgrounds, but variation must be intentional and legible:
@@ -47,6 +57,7 @@ Use these as conservative minimums on a 960 x 540 canvas. Increase height when u
Additional rules:
- Do not put long Chinese sentences or long English phrases into `height=18` or `height=22` boxes. Those heights are for short labels only.
- Text must fit both its own text box and its nearest visible container. A card, pill, footer bar, or table band should provide enough width and height for the visible wording with padding; do not rely on clipping, browser overflow, or SVG default wrapping to hide mistakes.
- Footer/source text should usually be one short line. If it needs more, make it a real caption block above the footer area.
- Bottom conclusion bars should be at least `40` px tall for one emphasized line and at least `54` px tall for two lines.
- Diagram labels should be short enough to fit the shape. Prefer two short lines over one cramped long line.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff