--- name: scene-dev description: > NextFrame Scene 组件开发规范。创建新 scene、修改 scene、审查 scene 质量。 确保每个 scene 满足 ADR-008 强制契约:4 接口 + 主题预设 + AI 元数据 + preview + validate 全绿。 TRIGGER: "写组件"、"新 scene"、"做 scene"、"加组件"、"scene 开发"、"组件开发"、 "写背景"、"写标题"、"写图表"、"写叠加层"、"新 scene"。 DO NOT TRIGGER when: 使用已有 scene 写 timeline、修 engine 代码。 --- # NextFrame Scene 开发规范(ADR-008 强制契约) ## 核心原则(违反 = 打回重做) ### 1. 一个 scene = 一个视觉原子 **一个 scene 只做一件事。** 不混合多个视觉职责。 | 正确 | 错误 | |------|------| | flowDiagram 只画节点+箭头 | flowDiagram 同时画标题+流程图+引语 | | titleCard 只画标题 | titleCard 同时画标题+代码块 | | subtitleBar 只画字幕 | subtitleBar 同时画字幕+进度条 | **判断标准:如果你的 scene 里有两种"能独立存在"的内容,就该拆成两个 scene。** 标题是标题 scene。引语是引语 scene。流程图是流程图 scene。组合靠 timeline 的 layers。 ### 2. 参数不写死视觉 - 颜色、位置、大小 → 全部走 params,不硬编码 - 文字内容如果支持 HTML(加粗/变色)→ 在 meta.params 里标注 `semantic: "supports inline HTML"` - 不支持 HTML 的文字 → 必须 `esc()` 转义 ### 3. 先预览后提交 写完 index.js + preview.html → **必须打开 preview.html 自己看一遍** → 确认无溢出、无错位、动画流畅 → 才能提交。 ## 开发流程(必须按顺序,每步都有阻断检查) ``` 1. 确定职责 → 这个 scene 只做什么?(一句话说清,超过一句就该拆) 2. 确定比例 + 类别 → 创建目录 3. 写 index.js(meta 全字段 → render → screenshots → lint) 4. 写 preview.html(自包含,file:// 可打开,不依赖服务器) 5. ⛔ 打开 preview.html 自己看 → 确认视觉正确 6. validate-scene.js 全绿 7. 测试 lint 拦截(故意传错参数) 8. 做一个 demo timeline 验证组合效果(和其他 scene 叠加) 9. 提交 ``` **步骤 5 是阻断点。** 不看 preview 不许提交。看到问题必须修完再走下一步。 ## 目录结构(强制) ``` src/nf-core/scenes/{ratio}/{category}/{sceneName}/ ├── index.js ← 必须:meta + render + screenshots + lint └── preview.html ← 必须:自包含,file:// 直接打开 ``` 比例目录(禁止 universal):`16x9/` | `9x16/` | `4x3/` 类别目录:`backgrounds/` | `typography/` | `data/` | `shapes/` | `overlays/` | `media/` | `browser/` ## index.js — 4 个必须导出 + 强制 meta 字段 ### meta(全字段必填) ```js export const meta = { // ─── 身份 ─── id: "sceneName", // 唯一 ID version: 1, // 改接口就 +1 ratio: "9:16", // 必填,禁止 null,和目录一致 // ─── 分类与发现 ─── category: "backgrounds", // 小写 label: "Scene Name", // 英文名 description: "中文描述,说清楚视觉效果和动画行为", tags: ["tag1", "tag2", "tag3"], // 至少 3 个搜索标签 mood: ["calm", "energetic"], // 情绪标签 theme: ["tech", "business"], // 适用主题 // ─── 渲染 ─── tech: "canvas2d", // canvas2d | webgl | svg | dom | video | lottie duration_hint: 12, // 建议时长(秒) loopable: true, // 能否循环 z_hint: "bottom", // bottom | middle | top // ─── 主题预设(至少 3 个)─── default_theme: "theme-name", themes: { "theme-name": { /* params 子集 */ }, // 至少 3 个预设 }, // ─── 参数 ─── params: { paramName: { type: "number", // number | string | boolean | color | enum | array | object | file default: 270, // 必须有(除非 required: true) range: [0, 360], // number 必须有 step: 1, // number 必须有 label: "中文名", // 必须有 semantic: "english desc for AI", // 必须有,写清楚含义和取值范围的效果 group: "color", // content | color | style | animation | shape }, }, // ─── AI 指南(全字段必填)─── ai: { when: "什么场景适合用,中文", how: "怎么在 timeline 里用,中文", example: { /* 完整 params 示例 */ }, theme_guide: "每个 theme 的中文一句话说明", avoid: "什么情况不要用", pairs_with: ["scene-id-1", "scene-id-2"], }, }; ``` ### render(t, params, vp) → HTML string ```js export function render(t, params, vp) { const { width, height } = vp; // 画布尺寸(像素) // 返回 HTML 片段 } ``` **强制规则:** - 纯函数 — 相同 (t, params, vp) → 相同输出 - 禁止 Math.random()(除非用 seed)、Date.now()、全局状态 - 禁止 setTimeout / setInterval / requestAnimationFrame / fetch - 返回的 HTML 宽高 = vp.width × vp.height - **只做自己那一件事的 HTML。** 不画别的 scene 该画的东西 **按 tech 类型的输出格式:** | tech | 返回什么 | 注意 | |------|---------|------| | canvas2d | `` + `` | canvas 尺寸 = vp,不要写死 1080 | | svg | `` | viewBox 天然缩放 | | dom | `
` | 尺寸跟 vp 走 | | webgl | `` + WebGL init `