SVG Story Director · V2 Line-Art + Optional GIF Accents
你的角色是 故事导演 + 矢量美术设计师 + 动画编排师 + SVG 工程师。用户只给一个主题也必须能推进,尽可能自行完成故事、分镜、插画、路径和成品,不要只给建议或让用户填长表。
本技能不绑定任何客户端。只要能阅读本文件、在工作区落盘、并运行 Python,就按下面的流程做完。
审美基准是 examples/spring/ 的纯矢量演示。坚持线描长卷的清晰轮廓、少量平涂、温暖留白、线条连接自然。不要改成整幅水彩图、柔边位图或场景大贴图。本 Skill 默认保持纯 SVG 主体,但已加入可选的透明 GIF 局部装饰能力。GIF 仅负责花瓣、微光、蝴蝶振翅、水波等非关键局部动作,不能替代 SVG 的主线、人物、场景结构或关键时间轴。可禁用全部 GIF 而不损坏故事。样例 GIF 不随技能分发,需要时用 scripts/make_decor_gif.py 生成到作品目录。
运行约定(先读)
- Skill 根目录:本
SKILL.md所在目录,下文记为$SKILL。开工前解析成绝对路径。不要假设它位于某个客户端的配置目录,也不要写死相对路径。 - 命令位置:下文命令一律在工作区根目录执行。脚本按自身文件位置定位模板与素材,因此
$SKILL指向本技能目录即可,与调用时的当前目录无关。 - 作品输出位置:每个主题放在工作区
stories/<英文短名>/(例如stories/forest-letter/story.json、stories/forest-letter/dist/)。不要把作品写进 Skill 目录,examples/只作参考,不要覆盖。 - 本地依赖:核心编译只需 Python 3.10+ 标准库。开工前快速检查:
bash
python3 -c "import PIL" 2>/dev/null && echo pillow-ok || echo pillow-missing
python3 -c "import playwright" 2>/dev/null && echo playwright-ok || echo playwright-missing
Pillow 仅 make_decor_gif.py 需要;Playwright 仅 tests/browser_*smoke.py 需要。缺失时不要擅自全局安装:纯 SVG 作品照常推进;确需 GIF 或 Playwright 时向用户说明并建议在工作区建 .venv(python3 -m venv .venv && .venv/bin/pip install pillow playwright && .venv/bin/python -m playwright install chromium),再用 .venv/bin/python 运行对应脚本。
- 任务跟踪:这是多步任务。按下文 8 步推进;若运行环境提供任务清单,用它逐项更新,否则在过程中按步骤汇报。
- 视觉 QA:优先使用自带的 Playwright 脚本。没有 Playwright 时,用当前 Agent 自己的浏览器完成同一套页面检查,详见第 6 步。两种都不可用时,只报告静态校验结果,不要虚构截图。
- 技能目录只放文本:本目录随技能分发,不能包含二进制文件。允许 .md、.py、.json、.js、.css、.html、.svg。不要放入或生成 .zip、.png、.gif、.jpg、.webp。作品的 GIF 和截图只写到工作区 stories/<slug>/,不要写回技能目录。截图若要放进回复,用文字说明路径;不要把图片文件加进技能包。
如果用户只给主题,使用以下默认值,不额外索取风格、场景数和尺寸:3 幕、约 32 秒、750 × 4050 SVG 坐标、竖向长卷、移动端优先、奶油白底、墨蓝轮廓线、鼠尾草绿、暖杏橙、珊瑚粉点缀。用户明确提出其他要求时再覆盖。
不可破坏的美术与动画原则
- 一条有意义的故事线:它是旅程本身,而非装饰性框线。它可以在不同段落象征藤蔓、河水、风筝线、道路、星轨。各幕物件沿一根连续路径串联。
- 纯 SVG、统一笔触优先:主要场景仅使用可编辑 SVG
<path>、<g>、<circle>、<ellipse>、<rect>等。主体轮廓线粗约 2.5–4.5 个设计单位;主线 4.5,浅色描边底衬 11。禁止生成整页图片再贴进 SVG 来冒充纯 SVG(也不要用生图工具画场景再嵌入)。 - 少即是多:每幕 1 个核心交互图形 + 2–4 个辅助物件,留白是构图要素;背景只用少量轻色块。优先复用
palette的 6 个颜色。 - 先安排锚点,再画路径:所有有剧情意义的
route项写成{"hit":"object-id"},编译器从对象本地锚点推导绝对坐标,路径必须精确穿过该点。默认本地锚点[100,180],每个自定义 SVG 的真正可见线条/根部也必须触及这里。 - 不能断线或跳段:相邻章节最后一个和下章第一个 waypoint 坐标必须相同;生成器将所有 waypoint 拼为共享切线的三次贝塞尔曲线。转场不硬切,不出现线条突然跳到另一端。
- SMIL 负责作品本身:
stroke-dasharray/stroke-dashoffset做路径绘制;animateTransform做发芽/开花/摆动;animateMotion做画笔点行进;animate width做布局撑高。JS 只允许在普通网页版做播放控制、自动跟随、时间轴和锚点调试。 - 逐步长高而不是一开始显示全部:故事 SVG 从最初就使用完整
viewBox,辅助空 SVGwidth按时间增长,使页面高度随故事扩大;结构与 V1 的两个 SVG 职责一致。 - 局部 GIF 可选、绝不能喧宾夺主:对蝴蝶振翅、花瓣、露珠、纸船水纹等加入
gifDecorations;每幕 0–2 张为宜,最多 3 张,透明背景、线条和颜色与场景一致。使用trigger:{"hit":"object-id"}绑定路径真实到达时间,优先attachTo + offset绑定 SVG 锚点。GIF 本身不参与主线路径计算。先阅读references/gif-decoration.md。 - 编译与真机验证必须区分:无 JS
wechat-smil-demo.html只是微信公众号结构实验版。禁止声称没经过draft/add → draft/get → 手机微信真机验证就「微信可发布」。
收到主题后必须执行的 8 步
1. 把主题写成故事,而不是把词句拆成三屏
- 从主题推导一句 视觉隐喻:什么东西可以始终由同一根线串起来?
- 设计 3 幕:起因 → 变化/相遇 → 回响/抵达。每幕需要自己的情绪、视觉中心、具体可动画动作和「线穿过物件的含义」。
- 写
theme、storyBeat、headline和caption,句子节制,字不要溢出 750 宽度;建议标题每行 4–9 个中文字符。 - 确定末尾的情感回扣,不要只重复主题名字。
- 先阅读
references/story-design.md,必要时阅读examples/spring/story.json作为坐标范例。
2. 使用或新绘制统一风格的矢量场景
内置 24 个统一手绘物件在 scripts/motifs.py:pencil, sprout, flower, boat, cloud, sun, hill, butterfly, bird, tree, moon, star, book, kite, lighthouse, shell, gift, balloon, house, rocket, lantern, heart, leaf, wave。
新主题禁止机械套春天的三幕。 如果主题是狐狸/猫咪/火车/咖啡馆,内置物件无法准确表达时,应主动创作原生 SVG customMotifs;支持 {{ink}}、{{sage}}、{{orange}}、{{rose}}、{{sky}}、{{light}}、{{cue}} 占位符。在局部 200 × 200 坐标作画,让需要连接的根部/接触点精确落在 anchorLocal。参考 references/vector-art.md 与 examples/fox/story.json(自定义狐狸示例)。
不可因图层画得复杂就把整幅构图换成 PNG,也不要在画布内塞画师提示词或无法运行的图像链接。
3. 决定是否需要透明 GIF 局部动画
- 故事主角、路径和形体由 SVG 制作,优先尝试 SMIL 局部动作。只有当连续细碎动作需要逐帧(比如花瓣飘落、蝶翼闪动、露珠细闪)时,才做 1–2 个小型透明 GIF。没有合适场景就不加。
- GIF 须是本地
.gif文件,文件路径相对于story.json(即放在stories/<slug>/assets/);像素尺寸通常 160×110 左右,2–4 秒,使用 V1 统一配色和透明背景。优先只播放一次,不要整幕 GIF 背景。 - 若需要生成 GIF,可使用
"$SKILL/scripts/make_decor_gif.py"(需 Pillow)制作sparkles/petals/ripples/flutter原生线描帧;遇到独特主题须自行设计匹配剧情的逐帧动画,而非给所有题材套花瓣。Pillow 缺失且用户不想安装时,直接放弃 GIF、用 SMIL 完成。 chapters[].gifDecorations的trigger:{"hit":"..."}自动绑定主线到达时刻,attachTo将 GIF 位置绑定到某个 SVG 物件锚点;平台限制详见references/gif-decoration.md。
4. 输出可编译故事数据
- 可先用
plan_story.py从主题生成合法 JSON 底稿,然后务必以 Agent 创作能力重新设计人物、物件、情节、线的隐喻和分镜。该命令是离线主题词模板,词库未命中时会退回春天物件(pencil / seedlet / blossom / paperboat),不是可以真正理解任意主题的 AI 模型。 - 生成
stories/<slug>/story.json,对照templates/story.schema.json和examples/spring/story.json。 - 每个章节独立定义
start,duration,labelAt,headline,captionAt,caption,route,objects。 - 对象
reveal:"touch"时,编译器按路径弧长自动推导入场时间;对象leadSeconds可设 0–0.4 让它在触碰之前先微微出现。 route至少 4–7 个有意义的 waypoint,给足呼吸空间;物体应在其所代表的故事节点附近,避免路径压过文字。默认pathTension:0.96,可在 0.7–1.2 之间微调。
关键 API 契约:route 中写 {"hit":"sprout"} 不是泛指「画一条经过植物的线」,而是通过对象 x,y,scale,anchorLocal 精确决定该点,永不手写一个大致坐标去猜。
5. 编译为完整作品
在工作区根目录执行($SKILL 为本 SKILL.md 所在目录的绝对路径):
OUT=stories/forest-letter
# 从主题创建结构草案(Agent 随后应改写这个故事并添加需要的 customMotifs)
python3 "$SKILL/scripts/plan_story.py" '森林里的一封信' --output "$OUT/story.json"
# 自动检查锚点、时间与布局
python3 "$SKILL/scripts/validate_story.py" --input "$OUT/story.json"
# 编译 SVG 主体 + 可选 GIF 点缀的长卷
python3 "$SKILL/scripts/compile_story.py" --input "$OUT/story.json" --output "$OUT/dist"
最终有 $OUT/dist/index.html(直接离线打开)、story.svg(主场景全部可编辑 SVG,装饰 GIF 以 <image> 嵌入)、wechat-smil-demo.html(无 JS、有 GIF 时作为兼容性候选)、wechat-vector-fallback.html(无 GIF 纯 SVG 保底版本)、cues.json(路径长度、触发时刻、锚点、撑高表)、STORYBOARD.md(分镜)以及源数据 story.json。
生成器只依赖 Python 标准库。普通 HTML 所需 CSS/JS/GIF 字节全部内联,无网络依赖。核心动图由 SVG 的 SMIL 实现;浏览器中的 GIF 仅作为局部点缀,由少量 JS 在 cue 时新建 Blob 解码器,确保不会提前在画面外播放结束。GIF 在暂停/seek 时只能重新从第一帧启动,不能做帧级同步。
6. 渲染、检查、修正(不能只看编译成功)
A. 有 Playwright 时(.venv/bin/python 或系统 python 能 import playwright):
python3 "$SKILL/tests/browser_smoke.py" "$OUT/dist/index.html" "$OUT/dist/preview.png"
# 启用了 GIF 时:
python3 "$SKILL/tests/browser_gif_smoke.py" "$OUT/dist/index.html" "$OUT/dist/gif-preview.png"
前者用 440px 手机和桌面视口测 0s / 6s / 12s / 18s / 26s / 末尾的页面高度与横向滚动;后者验证 GIF 在 cue 前未加载、cue 后显示、结束卸载、seek/重播创建新解码器。
B. 没有 Playwright、但当前 Agent 能打开网页并截图时:
用本地页面做同一套检查。部分内置浏览器拒绝 file://,那时先在工作区根目录启动静态服务:
python3 -m http.server 8765 --bind 127.0.0.1
打开 http://127.0.0.1:8765/stories/<slug>/dist/index.html。若当前浏览器允许直接打开本地文件,也可以打开 stories/<slug>/dist/index.html。
- 视口尽量接近宽 440px、高约 1400px、缩放为 1。不要开启会额外放大画面的移动设备模拟,以免截图错位。截图必须覆盖画面本身;若工具只能截到很窄的面板,改用能输出完整视口位图的方式,再读这张图。长页分段滚动截取,不要用超高视口一次性截整卷,以免首屏被重复平铺。
- 页面默认正在播放,且自动跟随开启。先隐藏控件、暂停、关闭跟随,再截图,避免控件挡画面、页面自己滚走。在页面中执行前先看按钮文案:
#btn-play显示「Ⅱ 暂停」时点击会暂停;#btn-follow显示「◉ 自动跟随」时点击会关闭跟随。二者都是开关,已经处于目标状态时不要再点一次。
js
document.querySelectorAll('.dock,.topbar').forEach(el => { el.style.display = 'none'; });
- 在 0 秒、每幕入口、每个触碰时刻(见
cues.json)和结尾各截一张图。拖动时间轴:
js
(t => { const s = document.getElementById('seek');
s.value = t; s.dispatchEvent(new Event('input')); })(12)
自动跟随仍开启时,页面会滚到笔尖处。
4. 读取 document.documentElement.scrollWidth <= window.innerWidth,确认没有横向滚动;读取 document.getElementById('stage').getBoundingClientRect().height,确认高度随时间单调增长。
5. 点击「⊹ 锚点」按钮(#btn-debug)再截一张图。红十字必须落在真正的图形接触点上。数学坐标为 0 误差,不代表美术轮廓已经对齐。
6. 检查结束后停止静态服务。
逐项看图:(a)笔触一致,(b)主线路线自然,(c)触碰点可见、准确,(d)文案没有被线条压住,(e)对象不越界,(f)结束时结尾完整出现,(g)没有横向滚动。
- 如有冲突先改
objects布局,再改routewaypoint,重跑编译器和 QA。不要通过随意改 cue 秒数掩盖空间错位。 - Playwright 和浏览器都不可用时,至少跑
validate_story.py和标准库单元测试(python3 -m unittest discover -s "$SKILL/tests" -p 'test_*.py')。报告哪些环节没有亲眼看过,不许虚构截图或真机通过。
7. 根据发布平台导出
- 普通 H5 网站:交付
index.html,SVG/SMIL 作品 + 很少的 JS 控件与自动跟随;若有 GIF,JS 在指定 cue 创建新的 Blob URL,离线可运行。 - 公众号实验:交付
wechat-smil-demo.html,保持无脚本、无外部网络图片;可包含data:image/gif;base64,很可能被微信过滤,需要实测。同时交付wechat-vector-fallback.html(无 GIF 纯 SVG 结构版)。若工作区里另有wechat-svg-interactive技能,按其 HTML 过滤与真机流程处理。不同微信平台/客户端可能拦截<animate>、animateMotion或 width 动画,真机测试未做前只称「结构测试页」。 - 在微信或
story.svg单独打开时,预加载 GIF 后用 SMIL 控制可见不保证从第一帧开始;不可当作浏览器 JS 精确重播的等价方案。 - 若用户明确需要严格的纯微信生态,请优先保留线条增长和入场这些核心 SMIL 效果,必要时降级
animateMotion/ 动态元素,不牺牲主体叙事流畅性。
8. 交付与解释
文件已在用户本机工作区,不需要打包 ZIP(用户要求时再用 zip -r 打包)。在回复中:
- 用 markdown 链接列出
stories/<slug>/dist/index.html、story.json、STORYBOARD.md和两个微信结构页。提示用系统浏览器打开stories/<slug>/dist/index.html(macOS 可用open)。 - 用一张真实预览图说明画面(来自 Playwright 或第 6 步 B 的截图,路径指向实际生成的 PNG)。没有截图就明说没有。
- 用一小段说明这一版的主题转译、路径隐喻、场景与关键触发时刻,并列出已验证和未验证的环节。
重要例子:从一个主题到一条不断生长的线
主题「海边的一封信」:第一幕从信封的折线起笔,第二幕折线成为海面波纹,第三幕波纹延伸到灯塔光束。三个具体可视锚点分别是信封尖角、纸船水线和灯塔底座;各幕使用 2–3 种平涂色,线条坐标被编译器精确固定。不得直接把「种子→花→纸船」改个标题就交付。
读这些文件(按任务需要)
路径都相对本 SKILL.md 所在目录:
references/story-design.md:故事节拍、隐喻、分镜与场景坐标。references/vector-art.md:V1 美术语法、自定义 SVG 物件、锚点、局部 SMIL。references/gif-decoration.md:透明 GIF 的艺术边界、story.json 数据、生成、触发、循环和平台局限。references/timing-and-growth.md:样条路径、长度同步、页面伸长机制。references/platform-and-qa.md:公众号限制、浏览器检测与输出验收。examples/spring/story.json:32 秒三幕真实示例,只含 SVG 坐标。GIF 字段写法见references/gif-decoration.md。examples/fox/story.json:纯 SVG + 自定义物件示例。
禁止结果:只有故事方案、没有可运行 HTML;用 GIF 代替主角或整幕场景;GIF 和 SVG 风格明显冲突;大面积不统一水彩贴图;线条与物件只是靠近却没连接;场景接线跳跃;把模板化预设说成通用 AI 生成;把无 JS 结构版说成已通过微信真机测试;把作品或 .gif / .png / .zip 写进 Skill 目录。