--- name: interflow-video-cut description: Turn a local talking-head / 口播 video into metadata, transcript, and an AI-composed card-based video where the agent composes cards by cloning styled templates and filling them (designing bespoke only when content needs it), then renders to MP4. Ships with the Interflow AI club sign-off ("Interflow AI 出品"). Use when the user asks for interflow-video-cut, 口播成片, talking-head repurposing, video takeaways, transcript cleanup, or turning a spoken video into a captioned card video. --- # Interflow Video Cut — 口播成片 Workflow Interflow Video Cut converts a local video into a card-based composition: the agent designs the cards (timing + content), assembles one composition HTML, and renders it to MP4 via `hyperframes`. **Default authoring mode: clone-and-recompose, not free-design.** Per card, start from a shipped fragment (`references/styles/.html`); always swap the text + accent, and **recompose its layout freely to serve the card's point — while holding the style's DNA fixed** (fonts, theme palette, ornament, spacing scale, overflow guards). Cloning anchors the quality floor in the style's *tokens*, not in a frozen arrangement, so adjacent cards need not look identical. Hand-authoring from a blank file is the **exception**, only when content fits no reference. ## CLI Resolution `vtake` CLI (`npx -y @notedit/vtake@latest`) handles extract/transcribe; `hyperframes` (`npx -y hyperframes@latest`) renders. Both auto-download from npm. `SKILL_DIR` = this skill's dir (host injects it as "Base directory for this skill: …"); all `references/…` + `scripts/…` paths are relative to it. ## Reference Map — read the right file at the right step The workflow skeleton is below; the heavy detail lives in `references/`. Clone from the library, don't reinvent. **Loading discipline (progressive disclosure — read this first).** Every file below is **Level 3**: it costs zero tokens until you open it, so open it **only when you actually branch into its step, and read only the slice you need** — not the whole file "to be safe." Concretely: (1) **never pre-read** these at the start of a run; pull each one at the moment its step arrives. (2) When a file is a catalog/matrix (`DESIGN_INDEX.md`, the resolution tables in `render-strategy.md`), read **the chosen style's / ratio's row**, not the entire matrix. (3) On the **fast path** — the user pre-approved a single known preset (e.g. says "默认 / glass-hud / 直接产") — skip `render-strategy.md` **and** `DESIGN_INDEX.md` entirely and go straight to that preset's recipe + the one style fragment it clones. Reading all four heavy refs on every run defeats the whole point of moving them out of `SKILL.md`. | file | what's inside | read at | |---|---|---| | `references/DESIGN_INDEX.md` | full style × layout × frame matrix + a decision guide + the 中文风格词库 (中文名 ↔ key) | Step 6 / 7 | | `references/render-strategy.md` | **Step 7 in full** — the 5-question call (Channel A `AskUserQuestion` + Channel B plain-text), precompute rules, day/night auto-match, canvas/frame/bounds resolution tables, theme palettes, 留白, camera rhythm (运镜), layout compositions, storyboard render contract | Step 7 | | `references/card-contract.md` | **Step 8 in full** — clone-first steps, the card HTML contract + hard rules, NON-NEGOTIABLE overflow safety, portrait sizing, motion philosophy, the `data-anim` kinds table, and the `card-cta` brand-outro template | Step 8 | | `references/composition-assembly.md` | **Step 9 reference detail** — GSAP statement cheat sheet, timing validation, video framing reference, HyperFrames QA rules, hard-won gotchas, the fixed card-cta GSAP block | Step 9 | | `references/composition-shell.html` | the STATIC composition scaffold you `cp` and fill (do NOT retype it) | Step 9 | | `references/transitions-dev-motion.md` | **transitions.dev motion kit** — 7 ported product-UI component effects (digit-flip / lines-reveal / blur-swap / badge-pop / check-draw / icon-swap / shimmer) + 2 handoffs (page / reveal), the CSS-cubic-bezier→GSAP curve map, required card HTML/CSS hooks, and the **content-driven default-by-content table**. The kit is the default surface for graphic cards (every video shows crafted micro-motion, not plain text). | Step 6 / 8 (any graphic card) | | `references/impact-motion.md` (+ `impact-motion/playground.html` live demo, `?seek=` freezes frames) | **impact-motion kit(炸裂族动效)v2** — 12 hard-arrival kinds (type-on / char-scramble / word-slam / shake / light-sweep / stamp + mask-lines / magnetic-assemble / ink-bleed / rgb-split / split-flap / flip-3d) + 4 exit transitions (shatter / blinds / flash-cut / punch) + `slam` beat + `videoPunch` 镜头推近, compiled by `build-timeline.py`, seeded & seek-safe. Contains **SLAM DNA 三铁律**, the **Style × impact 准入矩阵** (incl. 歸藏 clean family + light-theme ink flash), and the **冲击自动分配** signal table. Whole-film budget: **≤3 impact uses**(炸裂,但克制). | Step 6 (自动分配) / 8 (writing impact cards) | | `references/card-shots.md` | **卡片分镜库(形状层)** — 7 个整卡多幕编排预设(number-landing / list-cascade / quote-hold / compare-split / mechanism-draw / token-cycle / headline-stack),签名动作 + 槽位契约 + 后 50% 揭示默认,`build-timeline.py` 编译(`scripts/card_shots.py`)。含 **词时触发**(`data-anim-at-word` / 槽 `atWord` / 字符串 `videoPunch`——揭示踩说话人真实词时)与 **direction 导演块**(前铺后冻 / 构图轮换 / 呼吸帧 lint,opt-in)。内容→分镜默认表在文内。 | Step 6 (选分镜/导演块) / 8 (写 shot 卡的 DOM 契约) | | `references/narrative-progression.md` | **叙事递进编排** — when the transcript *argues / explains* (mechanism, cause→effect, this-vs-that, setup→payoff), 4 arcs (问题→机制→产出 · 分阶段揭示 · 对比递进 · 认知翻转) that sequence cards + stage each card's reveal so motion *carries the logic*. Expressed entirely in `beat`/`transition`/`data-anim`/`zone` — no new visual style. Skip for a flat list of independent takeaways. | Step 6 (sequencing an explainer) | | `references/editorial-print-montage.md` | the multi-asset montage kit for the `editorial-print` style (5 layout primitives, 3 transitions, asset staging, woven vs standalone modes) | when style = editorial-print | | `references/styles/*.html` | self-contained style fragments — **clone these**; includes the 炸裂族 (neon-grid-hud / liquid-aurora / holo-iridescent + playground-gallery DNA) | Step 7 / 8 | | `references/styles/guizang-dna.md` (+ `guizang-clean.css` 组件 + `guizang-checklist.md` 质检) | **简洁排版类·歸藏家族**的 DNA 纪律层(两支柱、字号字重耦合、预设调色板、组件库)。统管 swiss / minimal / terminal / editorial-print / pastel-aura | 选了 clean 家族任一款时(Step 7/8)| | `references/components/components.md` (+ `components-gallery.html` 渲染 demo / 抬取源) | **视觉素材组件库·高频包**(归藏家族新成员,13 个内容形态:折线/柱/环形/大数字 · 类比/黑盒/等式/光谱/冰山/对照 · 终端/对话/代码)。内容命中某种形态时抬一个组件进卡,质量锁死在规格上。含 content→组件 查表。**仅 clean 家族**,CSS 在 guizang-clean.css | clean 家族卡内容命中结构形态时(Step 6 选形 / Step 8 写卡)| | `references/layouts/*.html` | layout skeletons (videoBounds + cardBounds, landscape/portrait); neon-grid-hud carries its own window-scene | Step 7 / 9 | | `references/frames/*.html` | 3 frame chrome fragments (clean / hairline / polaroid) | Step 9 | | `references/curve-motion/README.md` | **可选视觉增强** — 数学曲线母题,**按用途分流**:氛围底(任意曲线) · 斜字 textPath(**仅开放曲线 `openKeys`**,闭合曲线排字必溢出) · clip 裁图剪影(**仅闭合曲线**) · 采样点布点(任意) + 沿曲线丝滑入场漂移(`curveIn`/`curveBob`)。全闭式 `draw(t)`、确定性 seek 安全;配暗底风格(nebula-glass/glass/spatial/geom),不叠 glass-hud/swiss/minimal。**Step 7 的曲线适配风格会触发一次 curve-intent 条件追问**(见 render-strategy.md「5b」) | Step 7 / 8 / 9(想加高级动态质感时) | ## Workflow ### Interaction contract — 前置一次问全,然后无人值守跑完(ALL channels) **The user hands you a video to get back a finished film, not to babysit a pipeline.** So the ONLY moment you ask the user anything is **once, upfront** (Step 3.5), in a single batched round — ratio / layout / style / 运镜 / card density. After they answer, you run the **entire** pipeline (transcribe → correct → storyboard → cards → assemble → render) **autonomously to a finished MP4, with no further questions** — so they can walk away and come back to a complete work. This ordering is **identical on every invocation channel** (手机 CLI, 飞书 bot, desktop) — the channel only changes the question *transport* (native `AskUserQuestion` vs plain-text), never the *timing*. Rules: - **Front-load, don't drip.** Never ask a question mid-pipeline that you could have asked in Step 3.5. The old flow asked visual direction at Step 7 (after transcribe) — that's the bug this contract fixes. Precompute everything askable from the **file alone** (metadata + color analysis) so the whole batch fires before any slow/mechanical step. - **Autonomous = render and deliver, not preview-and-wait.** On the autonomous path you self-QA with snapshots (Step 10) and go straight to render (Step 11) — the user reviews the **finished MP4** and asks for revisions after. Only drop into the interactive preview loop if the user explicitly says they want to watch/iterate live. - **Stop early only for a genuine blocker** (missing file, ASR rate-limit, a contradiction in the request) — not for a preference you could have batched. - If the user *explicitly* pre-answers in their opening message ("竖屏 glass 直接产") or says "你看着来/默认", skip Step 3.5's questions and proceed on those + the auto-recommendations. ### 1. Check Environment ```bash npx -y @notedit/vtake@latest doctor # confirm bundled assets: ls "/assets/fonts" "/assets/vendor/gsap.min.js" ``` Required: - `ffmpeg` / `ffprobe` (system) - `python3` (stdlib only) — for `scripts/auto-style.py`, the auto style-match by video color in Step 7.0 - `/assets/fonts/*.woff2`, `/assets/vendor/gsap.min.js` (bundled inside this skill, staged to work dir in Step 9) Optional: - `ELEVEN_API_KEY` — set → `vtake transcribe` hits ElevenLabs directly (no rate limit). Unset → proxy `https://vtake.app/api/transcribe` (**3 req/min/IP**; override via `VTAKE_TRANSCRIBE_ENDPOINT`). Strongly recommended on macOS for `hyperframes render`: ```bash export PRODUCER_BROWSER_GPU_MODE=hardware ``` ### 2. Create a Work Directory ```bash VIDEO_PATH="/absolute/path/input.mp4" WORK_DIR=".interflow-video-work/$(basename "$VIDEO_PATH" | sed 's/\.[^.]*$//')" mkdir -p "$WORK_DIR" ``` ### 3. Extract Audio and Metadata ```bash npx -y @notedit/vtake@latest extract "$VIDEO_PATH" --out-dir "$WORK_DIR" ``` Outputs: `metadata.json` (duration, width, height, fps) + `audio.mp3`. ### 3.5 Upfront Consultation — ask EVERYTHING here, once (then go autonomous) **This is the single user-interaction gate.** Everything askable is decidable from the **file alone** — no transcript needed yet — so ask it all now, before the slow steps, then run the rest untouched (per the Interaction contract above). **Precompute first (file-only, cheap):** ```bash python3 "/scripts/auto-style.py" "$VIDEO_PATH" --json # → {"mode","temp","recommend":[...],"recommend_cn":[...]} → recommendedStyle ``` - `recommendedRatio` from `metadata.json` aspect (≥1.5→16:9 · ≤0.7→9:16 · else 4:5). **iPhone `.MOV` caveat:** ffprobe may report a rotated `1920×1080` that displays as portrait — check `stream_side_data=rotation` before trusting the aspect. - `autoCount` from duration × **assumed-medium** density (Step 6 math) — label it "约 N 张"; you refine the exact count after the transcript exists. Good enough to ask. **Then ask ONE batched round** — the 5 visual questions. Use the best channel (native `AskUserQuestion`; else the plain-text template). The 5 visual questions + resolution tables live in [`references/render-strategy.md`](references/render-strategy.md) (read its question call now). Record all 5 answers in working memory: `ratio / layout / style / rhythm / cardDensity`. These drive Steps 6, 7, 8 — **do not re-ask any of them later.** (Channels without a native question tool: post the plain-text batch as one message, parse the reply, proceed. Same timing on every channel.) > If the user already stated preferences in their opening message, or said > "默认/auto/你看着来", skip the questions and use those + the auto-recommendations. > After this gate, you do not pause again until the finished MP4 (Step 12) — > unless a genuine blocker appears. ### 4. Transcribe (provider-aware: 火山 Seed ASR or ElevenLabs) Both produce the **same** `transcript.json` shape (`{ segments, words, raw }`, word- level timestamps in seconds) — the rest of the pipeline doesn't care which ran. ```bash if [ -n "$VOLC_ASR_API_KEY" ]; then # 火山引擎 Seed ASR 2.0 — 中文友好、便宜、词级时间戳。Key 走 env(不进 git)。 node "$SKILL_DIR/scripts/transcribe.js" --audio "$WORK_DIR/audio.mp3" --out-dir "$WORK_DIR" else # fallback: ElevenLabs via vtake (proxy mode works key-free, 3 req/min/IP) npx -y @notedit/vtake@latest transcribe "$WORK_DIR/audio.mp3" --out-dir "$WORK_DIR" --asr elevenlabs fi ``` Output: `transcript.json` with `{ segments, words, raw }`. **Two ASR providers, pick by env:** - **火山 Seed ASR** (`VOLC_ASR_API_KEY` set) — async submit (base64 audio, **no public URL needed**); `transcribe.js` resamples to 16k mono first, polls, and emits the same shape. Optional: `VOLC_ASR_RESOURCE_ID` (default `volc.seedasr.auc`), `VOLC_ASR_MODEL` (default `bigmodel`). Re-convert a saved result without re- billing: `node transcribe.js --from-result volcengine_result.json --out-dir "$WORK_DIR"`. - **ElevenLabs** (no `VOLC_ASR_API_KEY`) — `vtake` proxy mode runs key-free out of the box (zero model download) and only hits the rate limit below. There is no local/whisper path. **Rate limiting (proxy mode only — no `ELEVEN_API_KEY`):** the server allows 3 requests per minute per IP. If you see an error starting with `rate_limited:` or `service_busy:`, do **not** auto-retry — stop and tell the user how many seconds to wait (the message includes the retry hint), then resume from this step when they ask again. > **Note — this skill is additive only.** It *decorates* a take (cards over > video); it does NOT 去口水词 / 去气口 / 去废话. It assumes a reasonably clean > recording. If a raw 口播 has lots of 口水词 / 卡顿 / 重说, those stay in the > footage and get baked under your cards — clean it first with whatever editor > you prefer, then feed the cleaned video here. (Automatic rough-cut is no longer > wired into this pipeline.) ### 5. Correct Transcript Read `transcript.json` and fix obvious ASR errors: - Homophones, product names, technical terms, punctuation - Preserve all `start` / `end` timestamps - Prefer editing `segments[].text` only - Edit individual `words[].word` only for clear one-to-one replacements ### 6. Draft a Lightweight Storyboard (in chat) **No CLI involved.** Read `transcript.json` + `metadata.json` and design cards directly. `storyboard.json` is an agent-internal planning artifact — no vtake CLI command consumes it; it exists so you can think clearly about timing and content before writing each card's HTML. Keep the shape consistent with the example below so the same outline can drive the composition you author in Step 9: ```json { "schemaVersion": 3, "composition": { "fps": 30, "width": 1080, "height": 1920, "durationSeconds": 121.2, "layout": "portrait", "themeId": "noir", "seed": 42, "background": { "key": "dark-particles", "accent": "#7aa2ff", "opacity": 0.9 } }, "videoTrack": { "sourcePath": "input-video.mp4", "startSec": 0, "endSec": 121.2, "bounds": { "x": 0, "y": 0, "width": 1080, "height": 1920 } }, "subtitles": { "enabled": false }, "cards": [ { "id": "card-01", "intent": "Hook with the speaker's anxious midnight question", "startSec": 0.5, "endSec": 13.0, "accentIndex": 0, "zone": "fullscreen", "beat": "kinetic", "contentHints": { "kicker": "AN HONEST QUESTION", "title": "晚上 11 点的灵魂提问", "detail": "客户六十秒语音:「人民币会升值,我的美金保单是不是亏惨了?」" } } ] } ``` **Required Card fields:** | field | type | purpose | |---|---|---| | `id` | string | stable id used in card HTML & GSAP selectors | | `intent` | string | natural-language description; fed to card synthesis | | `startSec` / `endSec` | number | times in seconds (endSec > startSec) | | `accentIndex` | 0 \| 1 \| 2 \| 3 \| 4 | which of the 5 theme accent colors this card pulls | | `zone` | enum (see below) | where on the canvas the card lives | | `contentHints` | object | free-form bag; agent puts kicker/title/detail/data/quote here | | `archetype` (optional) | string | free-form label you may attach to remember a card's pattern; absent = free-form, which is the default | | `beat` (optional) | enum: `settle` \| `cut` \| `hold` \| `kinetic` \| `montage` \| `footage` \| `slam` | **the card's motion = its INTENT** (not a random gesture). `build-timeline.py` picks the whole enter/life/exit by this. Absent = `settle` (calm slip+drift, the legacy default). Assign by what the card means — hook→`kinetic` 或 `slam`(整卡逐块砸落+死静,impact 族、计入 ≤3 预算、卡需 ≥2.5s), 重锤/严肃→`cut`, 金句/留白→`hold`, 罗列/证据→`montage`, 视频当主角→`footage`. See the beat table in `composition-assembly.md`. | | `videoPunch` (optional) | (number \| string)[] — 秒(相对卡 start)或**要踩的词** | **说话人爆点瞬间的镜头硬推近**:`#video-wrap` scale 1→1.06 (0.14s power4.out) → 回 1 (0.3s)。字符串条目(如 `["私信"]`)= 从 transcript 词级时间戳解析、推近落在说话人**说出该词的瞬间**。两次间隔 ≥0.5s;一片 ≤2 次;不计入 impact 预算。 | | `shot` (optional) | `{key, slots, params}` | **卡片分镜预设**(形状层):一张卡的整段多幕编排由编译器展开——7 个预设(number-landing / list-cascade / quote-hold / compare-split / mechanism-draw / token-cycle / headline-stack),每个带签名动作 + 槽位契约 + 后 50% 揭示默认;槽可 `atWord` 踩词时。声明时接管 enter/life(beat 忽略);校验失败回落 beat。见 `references/card-shots.md`。留给值得编排的节点(一片 2-4 个),普通卡用 beat。 | | `transition` (optional) | enum: `cut` \| `fade` \| `slide` \| `wipe` \| `page` \| `reveal` \| `shatter` \| `blinds` \| `flash-cut` \| `punch` | card-to-card handoff (**now live** in `build-timeline.py`): `cut` = hard frame boundary (no slip overlap; set next card's `startSec` = this `endSec`), `wipe` = clip sweep, `fade`/`slide`/absent = the silky slip-up; `page`/`reveal` = transitions.dev handoffs; `shatter`/`blinds`/`flash-cut`/`punch` = impact kit(`flash-cut` is a hard boundary like `cut`; `shatter` needs the card ≥1.6s; ≤3 impact uses per film — see `references/impact-motion.md`). | | `styleKey` (optional) | string (a `references/styles/`) | which style fragment to clone this card from. Absent = the film's **primary** style. Set it only on **accent cards** (hero / big-number / outro) to pull a tonally-compatible **sibling** style — see the style-family note in Step 7. Colors stay coherent automatically: every fragment paints from `var(--accent-N)`, so a sibling inherits the theme palette. | | `componentKey` (optional) | string (a `references/components/`) | which **visual component** this card's content maps to (折线/柱/环形/大数字 · 类比/黑盒/等式/光谱/冰山/对照 · 终端/对话/代码). Set it when the transcript hits a structured shape (a trend, a ratio, an analogy, a dialogue…) — pick via the content→组件 table in `references/components/components.md`. Absent = free-compose (today's default). **clean 家族 only.** | **Composition-level `background` (optional)** — a moving atmosphere layer behind the cards / above the video. `{ key, accent, opacity }`; `key` ∈ backgrounds 库 (`dark-particles` / `starfield` / `aurora` / `grid-hud` / `mesh-gradient`) 或 `curve:<曲线>`(曲线**氛围底**——氛围归这层,不是前景斜字)。`build-timeline.py` 自动注入 `` + `draw(t)` 钩子(Step 9 记得 `cp` `backgrounds.js` + `curve-atmosphere.js` 进 `vendor/`)。选择在 Step 7「背景层」问题里决定(见 render-strategy.md)。全闭式 `draw(t)`、 seek 安全。 **Five `zone` values** (resolve to card-host pixel bounds in Step 9; full bounds table in `references/composition-assembly.md`): `fullscreen` (whole canvas — hero / big numbers) · `whiteboard-area` (40px inset or portrait bottom-45% — dense data) · `lower-third` (bottom 30% — annotate over video) · `side-panel` (landscape right-42% / portrait bottom-40% — data beside video) · `video-overlay` (full canvas, mostly-transparent card — overlay on full-bleed video). Video bounds are set **once** (`videoTrack.bounds`); "moving" video between cards = GSAP tweens on `#video-wrap` (Step 9). **冲击自动分配(pipeline 全自动 — drafting 时执行,用户不手选)。** 扫描修正后的 transcript,命中信号就自动分配 impact,按优先级取**最强 ≤3 个**(预算硬约束, 超了 build-timeline.py 会 WARN),其余一律回落 transitions.dev 丝滑 kit: ① **认知翻转**("但其实/真相是/大家都错了")→ break 卡 `transition: shatter` + 下一张 `word-slam` 立新观点(优先级最高);② **开场 hook / 全片最强主张** → `beat: "slam"`;③ **情绪爆点 / 章节硬切** → `flash-cut` 或 `punch` + 可选 `videoPunch` 踩在重音音节上;④ **命令 / 代码 / 工具名** → `type-on`。 完整信号表 + Style × impact 准入矩阵(歸藏 clean 家族哪些慎用)见 [`references/impact-motion.md`](references/impact-motion.md)。 **No prescribed roles or arc** — cards emerge from what the video says (all quotes, all data, open on a number or a story; let the transcript drive). **But when the transcript *argues or explains*** (a mechanism, cause→effect, this-vs-that, setup→payoff — you can say "first… which causes… which is why… so…" across the cards), reach for [`references/narrative-progression.md`](references/narrative-progression.md): 4 arcs that sequence the cards + stage each card's reveal so motion carries the logic, all in `beat`/`transition`/`data-anim` (no new style). Skip it for a flat list of independent takeaways — those stay平铺. **Card boundaries — one intent per card** ("let the transcript drive" ≠ "cut anywhere"). A boundary belongs where the speaker *pivots to a new idea*; a card spans the stretch between two boundaries. Signals: **topic shift** ("…now here's what works" = new card) · **argument unit** (a setup→proof→payoff >~20s = 2–3 cards, one per move) · **distinct claim/number/comparison** (each its own card, no 4-facts data-dump) · **emphasis/repetition** (repeated phrase / "the key point" = its own beat) · **shot change** (face→screen-share→prop). The math below sets *how many*; these set *where*. Disagree → trust the intents; never split one idea or fuse two to hit a number. **How many takeaways? — auto-infer from duration + density.** No fixed upper limit. Floor is fixed: **minimum 5 cards**. **Step 1 — base pace by duration** (natural sec/card for medium density): | video duration | base pace (sec per card) | |---|---| | < 60s (short reel) | **6–8s** | | 60s – 3 min | **8–12s** | | 3 – 10 min | **12–20s** | | 10 – 30 min | **20–35s** | | > 30 min | **30–60s** | **Step 2 — density multiplier:** High density (many numbers / distinct claims / staccato) **× 0.7** · Medium (mixed data + narrative) **× 1.0** · Low (slow narrative, one idea over many sentences) **× 1.4**. **Step 3 — compute:** ``` cardCount = max(5, round(videoSec / (basePace × densityMultiplier))) ``` Always sanity-check against content: the math gives a starting count, but the actual intents decide the final count. **Speaker presence — keep the talking head on screen** (unless the user wants a faceless explainer): cards annotate, they don't replace. Default = full-bleed/ large-PIP speaker on opening + closing cards, ≥ small PIP/side-panel where the speaker makes a personal point; data-heavy cards can go full-card briefly. **Distill, don't transcribe — a card is information design.** The #1 failure is pasting a spoken sentence onto a card; a headline is a *rewrite* (same meaning, ⅓ the words, point surfaced). Gate every card: (1) **one intent, one sentence** — can't → split; (2) **headline ≠ quote** — ~14-char title, demote the explanation to a small detail line; (3) **no two cards make the same point** — merge/reframe overlaps; (4) **shape follows content** — comparison→two-column, number→big figure+label, list→stacked rows, quote→large centered. Reject & rewrite if: `title` > ~14 中文字, `detail` is a verbatim clause, or the card is "the next sentence" not "the next idea". **Final card — append a customizable brand outro (`card-cta`).** After all content cards, append one `fullscreen` outro card (`contentHints`: `recap` + `purpose`) and extend `composition.durationSeconds` by ~3.5s to match its `endSec`. Lead with a **recap / payoff line rewritten from what the video said** (the earned content beat, not a logo flash) + a one-line **purpose / 延伸** under the wordmark. **Re-theme it to the composition's style** (reuse `--bg`/`--ink`/ `--accent-0`, swap mark/wordmark/tagline; keep the entrance choreography) — a clashing fixed outro is a defect. Ships the Interflow club sign-off (`Interflow` + `Interflow AI 出品`); for an outside creator swap/drop the brand, no hard-sell CTA, **strip AI-jargon** (赋能/闭环/leverage…). Full template + JSON + entrance choreography: `references/card-contract.md`. ### 7. Resolve Render Strategy (from the Step 3.5 answers — do NOT ask again) The visual system — ratio / layout / style / 运镜 / density — was **already chosen by the user in Step 3.5**. This step is pure **resolution**: turn those stored answers into concrete canvas size, frame, theme palette, and per-card bounds. No questions here (front-load contract). What this step does with the stored answers: - **Resolve canvas** from `ratio` (16:9→1920×1080 `landscape` · 9:16→1080×1920 `portrait` · 4:5→1080×1350 `portrait`). - **Map `style` → a specific style key** and **auto-pick the frame** from the layout × style matrix; **resolve `rhythm`** (fixed vs dynamic videoBounds). - **Refine `cardCount`**: in 3.5 you used assumed-medium density for the "约 N" label; now the transcript exists, so apply the real density multiplier (Step 6) and let the actual intents set the final count. - **Conditional follow-up already handled in 3.5:** if `style` ∈ {glass / nebula-glass / spatial / geom}, the curve-foreground (5b) and dark-background (5c) micro-questions fire as part of the upfront gate (the user is still there right after picking style) — not here. By Step 7 they're already answered. **→ The full resolution detail — the canvas / frame / bounds tables, theme palettes, day/night style map, 留白, camera rhythm, the storyboard render contract, AND (for reference) the original question call + plain-text fallback — is in [`references/render-strategy.md`](references/render-strategy.md).** Read only the slice for your resolved ratio/style. (Its "ask" machinery is what Step 3.5 invokes; here you read the **resolution tables**.) State back what's locked in one sentence (ratio + canvas size, layout, primary style + any sibling accent styles, frame, final cardCount, rhythm), then proceed. ### 8. Write Each Card's HTML **Clone-first (the default).** For each card: 1. `cp "$SKILL_DIR/references/styles/.html" "$WORK_DIR/public/cards/.html"` — `` is the card's `styleKey` if set, else the film's **primary** style (see the style-family note in Step 7: most cards use the primary; accent cards may pull a tonally-compatible sibling). 2. Rename the fragment's `data-card-id="ref-"` to this card's `` (update the scoped `