explainer-video 0.0.0-stage → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/.claude-plugin/plugin.json +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +154 -2
  4. package/cordis.patch.yml +7 -0
  5. package/lib/index.js +35 -0
  6. package/package.json +56 -4
  7. package/plugin.json +13 -0
  8. package/skills/coursework-author/.synced-from +5 -0
  9. package/skills/coursework-author/SKILL.md +209 -0
  10. package/skills/craft-codeflow-flows/SKILL.md +116 -0
  11. package/skills/craft-codeflow-mine/SKILL.md +207 -0
  12. package/skills/craft-codeflow-plan/SKILL.md +65 -0
  13. package/skills/craft-codeflow-scene/SKILL.md +168 -0
  14. package/skills/craft-video-align/SKILL.md +109 -0
  15. package/skills/craft-video-assemble/SKILL.md +70 -0
  16. package/skills/craft-video-audio/SKILL.md +59 -0
  17. package/skills/craft-video-enrich/SKILL.md +46 -0
  18. package/skills/craft-video-plan/SKILL.md +57 -0
  19. package/skills/craft-video-publish/SKILL.md +79 -0
  20. package/skills/craft-video-render/SKILL.md +68 -0
  21. package/skills/craft-video-scene/SKILL.md +112 -0
  22. package/skills/craft-video-script/SKILL.md +80 -0
  23. package/skills/craft-video-thumbnail/SKILL.md +168 -0
  24. package/skills/explainer-video-from-codebase/FLOWS.md +577 -0
  25. package/skills/explainer-video-from-codebase/SKILL.md +698 -0
  26. package/skills/explainer-video-from-codebase/tools/apply-flows.mjs +531 -0
  27. package/skills/explainer-video-from-codebase/tools/approve-flows.mjs +56 -0
  28. package/skills/explainer-video-from-codebase/tools/codemap-md.mjs +116 -0
  29. package/skills/explainer-video-from-codebase/tools/config/dotenv.mjs +13 -0
  30. package/skills/explainer-video-from-codebase/tools/config/json.mjs +14 -0
  31. package/skills/explainer-video-from-codebase/tools/config/redact.mjs +16 -0
  32. package/skills/explainer-video-from-codebase/tools/config/toml-lite.mjs +13 -0
  33. package/skills/explainer-video-from-codebase/tools/config/yaml-lite.mjs +18 -0
  34. package/skills/explainer-video-from-codebase/tools/excerpt-check.mjs +186 -0
  35. package/skills/explainer-video-from-codebase/tools/excerpt.mjs +116 -0
  36. package/skills/explainer-video-from-codebase/tools/flow-diagram.mjs +168 -0
  37. package/skills/explainer-video-from-codebase/tools/flow-plan.mjs +129 -0
  38. package/skills/explainer-video-from-codebase/tools/flows-md.mjs +87 -0
  39. package/skills/explainer-video-from-codebase/tools/lang/c.mjs +684 -0
  40. package/skills/explainer-video-from-codebase/tools/lang/go.mjs +673 -0
  41. package/skills/explainer-video-from-codebase/tools/lang/python-config.mjs +143 -0
  42. package/skills/explainer-video-from-codebase/tools/lang/python-deps.mjs +62 -0
  43. package/skills/explainer-video-from-codebase/tools/lang/python-stdlib.mjs +24 -0
  44. package/skills/explainer-video-from-codebase/tools/lang/python.mjs +609 -0
  45. package/skills/explainer-video-from-codebase/tools/lang/typescript.mjs +963 -0
  46. package/skills/explainer-video-from-codebase/tools/lib/decompose.mjs +102 -0
  47. package/skills/explainer-video-from-codebase/tools/lib/groups.mjs +77 -0
  48. package/skills/explainer-video-from-codebase/tools/lib/observe-coverage.mjs +68 -0
  49. package/skills/explainer-video-from-codebase/tools/lib/observe-v8.mjs +232 -0
  50. package/skills/explainer-video-from-codebase/tools/lib/observe.mjs +162 -0
  51. package/skills/explainer-video-from-codebase/tools/lib/redact.mjs +28 -0
  52. package/skills/explainer-video-from-codebase/tools/lib/sandbox.mjs +97 -0
  53. package/skills/explainer-video-from-codebase/tools/lib/test-links.mjs +28 -0
  54. package/skills/explainer-video-from-codebase/tools/lib/toml.mjs +129 -0
  55. package/skills/explainer-video-from-codebase/tools/lib/trace-c.mjs +95 -0
  56. package/skills/explainer-video-from-codebase/tools/lib/trace-go.mjs +101 -0
  57. package/skills/explainer-video-from-codebase/tools/lib/trace-js.mjs +136 -0
  58. package/skills/explainer-video-from-codebase/tools/lib/tree-sitter.mjs +19 -0
  59. package/skills/explainer-video-from-codebase/tools/lib/walk.mjs +138 -0
  60. package/skills/explainer-video-from-codebase/tools/mine.mjs +409 -0
  61. package/skills/explainer-video-from-codebase/tools/propose.mjs +127 -0
  62. package/skills/explainer-video-from-codebase/tools/rank.mjs +119 -0
  63. package/skills/explainer-video-from-codebase/tools/ranking-md.mjs +76 -0
  64. package/skills/explainer-video-from-codebase/tools/rules/c-handlers.mjs +53 -0
  65. package/skills/explainer-video-from-codebase/tools/rules/celery-taskiq.mjs +48 -0
  66. package/skills/explainer-video-from-codebase/tools/rules/click-typer.mjs +6 -0
  67. package/skills/explainer-video-from-codebase/tools/rules/express.mjs +72 -0
  68. package/skills/explainer-video-from-codebase/tools/rules/fastapi.mjs +99 -0
  69. package/skills/explainer-video-from-codebase/tools/rules/go-http.mjs +173 -0
  70. package/skills/explainer-video-from-codebase/tools/rules/go-test.mjs +108 -0
  71. package/skills/explainer-video-from-codebase/tools/rules/grpc.mjs +57 -0
  72. package/skills/explainer-video-from-codebase/tools/rules/gtest.mjs +97 -0
  73. package/skills/explainer-video-from-codebase/tools/rules/haystack.mjs +55 -0
  74. package/skills/explainer-video-from-codebase/tools/rules/jest-vitest.mjs +100 -0
  75. package/skills/explainer-video-from-codebase/tools/rules/js-http-client.mjs +111 -0
  76. package/skills/explainer-video-from-codebase/tools/rules/nextjs.mjs +53 -0
  77. package/skills/explainer-video-from-codebase/tools/rules/process-spawn.mjs +49 -0
  78. package/skills/explainer-video-from-codebase/tools/rules/py-http-client.mjs +26 -0
  79. package/skills/explainer-video-from-codebase/tools/rules/pytest.mjs +107 -0
  80. package/skills/explainer-video-from-codebase/tools/rules/react-router.mjs +17 -0
  81. package/skills/explainer-video-from-codebase/tools/rules/registry.mjs +31 -0
  82. package/skills/explainer-video-from-codebase/tools/trace/c/fmtrace-sh +14 -0
  83. package/skills/explainer-video-from-codebase/tools/trace/js/fmtrace-net.cjs +33 -0
  84. package/skills/explainer-video-from-codebase/tools/trace/js/fmtrace-srcmap.cjs +41 -0
  85. package/skills/explainer-video-from-codebase/tools/trace/python/fmtrace_plugin.py +87 -0
  86. package/skills/explainer-video-from-codebase/tools/trace/python/sitecustomize.py +330 -0
  87. package/skills/explainer-video-from-codebase/tools/trace/sandbox/linux-bwrap.sh +73 -0
  88. package/skills/explainer-video-from-codebase/tools/trace/sandbox/macos-nonet.sb +41 -0
  89. package/skills/explainer-video-from-codebase/tools/trace.mjs +577 -0
  90. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-c.txt +21 -0
  91. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-cpp.txt +21 -0
  92. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-go.txt +21 -0
  93. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-javascript.txt +21 -0
  94. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-python.txt +21 -0
  95. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-typescript.txt +21 -0
  96. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/web-tree-sitter.txt +21 -0
  97. package/skills/explainer-video-from-codebase/vendor/tree-sitter/MANIFEST.json +172 -0
  98. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-c.wasm +0 -0
  99. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-cpp.wasm +0 -0
  100. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-go.wasm +0 -0
  101. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-javascript.wasm +0 -0
  102. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-python.wasm +0 -0
  103. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-tsx.wasm +0 -0
  104. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-typescript.wasm +0 -0
  105. package/skills/explainer-video-from-codebase/vendor/tree-sitter/web-tree-sitter.js +4081 -0
  106. package/skills/explainer-video-from-codebase/vendor/tree-sitter/web-tree-sitter.wasm +0 -0
  107. package/skills/explainer-video-from-coursework/ARCHITECTURE.md +61 -0
  108. package/skills/explainer-video-from-coursework/PIPELINE.md +369 -0
  109. package/skills/explainer-video-from-coursework/ROADMAP.md +727 -0
  110. package/skills/explainer-video-from-coursework/SERIES.md +243 -0
  111. package/skills/explainer-video-from-coursework/SETUP.md +105 -0
  112. package/skills/explainer-video-from-coursework/SKILL.md +998 -0
  113. package/skills/explainer-video-from-coursework/scenes/_pt-chart.js +12 -0
  114. package/skills/explainer-video-from-coursework/scenes/_pt-equation.js +10 -0
  115. package/skills/explainer-video-from-coursework/scenes/_pt-hubspoke.js +7 -0
  116. package/skills/explainer-video-from-coursework/scenes/_pt-loopflow.js +7 -0
  117. package/skills/explainer-video-from-coursework/scenes/_pt-sequence.js +12 -0
  118. package/skills/explainer-video-from-coursework/scenes/_pt-timeline.js +11 -0
  119. package/skills/explainer-video-from-coursework/src/scene-code.html +127 -0
  120. package/skills/explainer-video-from-coursework/src/scene-code.js +379 -0
  121. package/skills/explainer-video-from-coursework/src/scene-core.js +1250 -0
  122. package/skills/explainer-video-from-coursework/src/scene-diagram.html +50 -0
  123. package/skills/explainer-video-from-coursework/src/scene-svg.html +50 -0
  124. package/skills/explainer-video-from-coursework/src/thumbnail.html +119 -0
  125. package/skills/explainer-video-from-coursework/src/timeline.js +297 -0
  126. package/skills/explainer-video-from-coursework/src/timing-chart.js +160 -0
  127. package/skills/explainer-video-from-coursework/tools/align.mjs +419 -0
  128. package/skills/explainer-video-from-coursework/tools/apply-enrichment.mjs +206 -0
  129. package/skills/explainer-video-from-coursework/tools/assemble.mjs +223 -0
  130. package/skills/explainer-video-from-coursework/tools/build-audio.mjs +234 -0
  131. package/skills/explainer-video-from-coursework/tools/chromium.mjs +73 -0
  132. package/skills/explainer-video-from-coursework/tools/debug-page.mjs +75 -0
  133. package/skills/explainer-video-from-coursework/tools/env.mjs +30 -0
  134. package/skills/explainer-video-from-coursework/tools/frame-dedup.mjs +131 -0
  135. package/skills/explainer-video-from-coursework/tools/key-source.mjs +151 -0
  136. package/skills/explainer-video-from-coursework/tools/model.mjs +21 -0
  137. package/skills/explainer-video-from-coursework/tools/naming.mjs +38 -0
  138. package/skills/explainer-video-from-coursework/tools/onset-audit.mjs +51 -0
  139. package/skills/explainer-video-from-coursework/tools/pacing-check.mjs +350 -0
  140. package/skills/explainer-video-from-coursework/tools/planner.mjs +616 -0
  141. package/skills/explainer-video-from-coursework/tools/publish.mjs +230 -0
  142. package/skills/explainer-video-from-coursework/tools/render.mjs +205 -0
  143. package/skills/explainer-video-from-coursework/tools/resolve-beats.mjs +239 -0
  144. package/skills/explainer-video-from-coursework/tools/script-check.mjs +174 -0
  145. package/skills/explainer-video-from-coursework/tools/stamp.mjs +71 -0
  146. package/skills/explainer-video-from-coursework/tools/thumbnail.mjs +224 -0
  147. package/skills/explainer-video-from-coursework/tools/timing-report.mjs +299 -0
  148. package/skills/explainer-video-from-coursework/tools/transcribe-gemini.mjs +96 -0
  149. package/skills/explainer-video-from-coursework/tools/tts-gemini.mjs +394 -0
  150. package/skills/explainer-video-from-coursework/tools/verify-audio.mjs +88 -0
  151. package/skills/explainer-video-from-coursework/tools/workdir.mjs +124 -0
  152. package/skills/explainer-video-from-coursework/vendor/plotly-basic.min.js +8 -0
  153. package/skills/explainer-video-from-coursework/vendor/plotly-basic.min.js.LICENSE.txt +16 -0
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: craft-video-script
3
+ description: Write and validate the spoken narration for one explainer-video. Stage 3 of explainer-video-from-coursework; run it alone to (re)write a video's script.
4
+ ---
5
+
6
+ # craft-video-script — write the narration
7
+
8
+ > Part of the `explainer-video-from-coursework` pipeline — **stage 3 of 8**, the first
9
+ > per-video stage. Self-contained: it needs an enriched plan, not a full run.
10
+ >
11
+ > The pipeline's tools and scene hosts live in the sibling `explainer-video-from-coursework` skill: run them as `node ../explainer-video-from-coursework/tools/<tool>.mjs`, resolved against this skill's folder. Run from the **scratch root** — the directory holding `out/`, which Stage 0 fixes (default: beside your notes) — so every `out/<name>/…` path in this stage resolves.
12
+
13
+ > **Timing.** Bracket the stage with the stamp pair so the series accrues a per-video audit;
14
+ > `end` must run before the stage counts as done (see *Timing* in the main skill):
15
+ >
16
+ > ```bash
17
+ > node ../explainer-video-from-coursework/tools/stamp.mjs begin --out out/<name> --video <NN> --stage 3
18
+ > node ../explainer-video-from-coursework/tools/stamp.mjs end --out out/<name> --video <NN> --stage 3
19
+ > ```
20
+
21
+ **Prerequisites:** `out/<name>/plan.enriched.json` from the enrich stage.
22
+
23
+ The agent writes `out/<name>/scripts/<NN>-<slug>.json`, then validates:
24
+
25
+ ```bash
26
+ node ../explainer-video-from-coursework/tools/script-check.mjs --script out/<name>/scripts/<NN>-*.json \
27
+ --plan out/<name>/plan.enriched.json
28
+ ```
29
+
30
+ **Done when:** the run exits **0** (`✓ script is valid`). An `UNCOVERED source
31
+ section` error — or any other error — means the script is incomplete.
32
+
33
+ **Script contract.**
34
+
35
+ - **Register** — a calm, methodical teacher who explains through story; each concept
36
+ opens as a concrete scenario, then unpacks step by step. No edge, no hot takes;
37
+ analogies welcome.
38
+ - **Granularity** — one segment per visual beat. Roughly 40–80 words; ≤130 words hard.
39
+ - **Per segment** — `key` (unique, short), `text` (spoken **verbatim**; no URLs,
40
+ markdown, or code), `style` (delivery direction), `visual` (a hint for the scene
41
+ stage), `covers` (the plan sections this segment narrates), and optional `pad` (hold
42
+ silence in seconds).
43
+ - **`covers`** — copy the exact section identity from `plan.enriched.json`
44
+ (`"src :: title @L<line>"`). Every assigned section must appear in some segment's
45
+ `covers`.
46
+ - **Budget** — target `planTargetMinutes`; stay within its 55%–125% band.
47
+
48
+ `videoIndex`, `title`, `form`, `planTargetMinutes`, `sources`, `artefacts` are copied
49
+ from the enriched plan's video; only `text`, `style`, `visual`, `covers` are authored.
50
+
51
+ <!-- sync:stage3-addendum -->
52
+ **Code-flow videos only.** A code-flow video's plan, `plan.json` or `plan.enriched.json`, carries
53
+ `"kind": "codebase"`, and its `forms` are `overview`, `flow` and `core`. A coursework plan has no `kind`; nothing
54
+ in this block applies to its videos.
55
+
56
+ A code video's narration is the reading time: a panel shows only the lines being narrated, each highlighted while
57
+ its sentence plays. The script for a code video adds these rules:
58
+
59
+ - **Open** on the concrete request, UI action or command that starts the flow.
60
+ - **One hop per segment.** Name identifiers in words; spoken text holds no code, no URLs and no markdown.
61
+ - **Narrate every line a panel will show**: the hop's narrated lines (`sectionSpans[].lines` in the plan, or the
62
+ lines you choose when that is `null`, at most 22), plus a cited test's def, call and assertion lines.
63
+ - **Branches.** Teach each `teach` branch in its own segment, naming its selector: the setting, flag or profile
64
+ that picks it.
65
+ - **Tests.** Describe a cited test by what it asserts. Call a hop "tested" or "pinned" only for a direct, unmocked
66
+ link, never for a `"strength": "reachable"` citation. For a traced run say "observed in the test suite", never
67
+ "in production".
68
+ - **Evals.** An eval hop that drives a production flow names that flow's video instead of re-narrating it.
69
+ - **Pause prompts** are rare and bounded, only for a block that must be read whole.
70
+ - The segment's text says "pause" out loud.
71
+ - It carries `"pause": {"dwell": <s>}`, with 0 < dwell ≤ 2 and `pad` equal to the dwell.
72
+ - A video has at most 3. `script-check` errors on a fourth, so split the video instead.
73
+ - **Secrets.** Never say a secret or a redacted value.
74
+ <!-- /sync:stage3-addendum -->
75
+
76
+ ## Maintenance
77
+
78
+ > If you modify this file, or the user asks to change this stage, tell the user that the
79
+ > main `explainer-video-from-coursework` skill's **"Stage 3 — /script"** section must be
80
+ > reconciled to match, and name that section in your reply.
@@ -0,0 +1,168 @@
1
+ ---
2
+ name: craft-video-thumbnail
3
+ description: Design the upload thumbnail for one video — a mildly tacky, attractive 1280x720 PNG with one clear hook over a still from the video's own scenes and, for a series, a "<playlist name>-Part X, <title>" label. Stage 6b of explainer-video-from-coursework.
4
+ ---
5
+
6
+ # craft-video-thumbnail — the upload thumbnail
7
+
8
+ > Part of the `explainer-video-from-coursework` pipeline — **stage 6b of 8**, between
9
+ > publish (6) and render (7). It needs the final upload title (`title.txt`, from stage 6)
10
+ > and the stage-5 scenes, but not the finished MP4, so it runs before the expensive render.
11
+ >
12
+ > The pipeline's tools and scene hosts live in the sibling `explainer-video-from-coursework` skill: run them as `node ../explainer-video-from-coursework/tools/<tool>.mjs`, resolved against this skill's folder. Run from the **scratch root** — the directory holding `out/`, which Stage 0 fixes (default: beside your notes) — so every `out/<name>/…` path in this stage resolves.
13
+
14
+ > **Timing.** Bracket the stage with the stamp pair so the series accrues a per-video audit;
15
+ > `end` must run before the stage counts as done (see *Timing* in the main skill):
16
+ >
17
+ > ```bash
18
+ > node ../explainer-video-from-coursework/tools/stamp.mjs begin --out out/<name> --video <NN> --stage 6b
19
+ > node ../explainer-video-from-coursework/tools/stamp.mjs end --out out/<name> --video <NN> --stage 6b
20
+ > ```
21
+
22
+ **Prerequisites:** `--dest/<NN>-<slug>/title.txt` (stage 6), the video's stage-5 scenes and
23
+ `manifest.json`, `out/<name>/plan.enriched.json`, and the Stage 0 playlist answer; on the
24
+ machine, headless Chromium (see `SETUP.md`) and `ffmpeg` for the phone-size preview.
25
+
26
+ > A `headless Chromium not found` / `Playwright cache not found` failure means Chromium is
27
+ > absent — relay the `SETUP.md` step to the user and re-run.
28
+
29
+ The output is `--dest/<NN>-<slug>/thumbnail.png`, beside `title.txt`: a 1280x720 PNG of at
30
+ most 2,000,000 bytes, exactly what `upload_yt_video` picks up. The upload takes
31
+ `thumbnail.png`, `.jpg` or `.jpeg` from the video's folder, PNG or JPG only, refuses a file
32
+ over 2 MB and ignores `thumbnail.svg`. It is rendered from an HTML template by the
33
+ pipeline's own headless Chromium, never by a paid image-generation API.
34
+
35
+ ### The brief
36
+
37
+ A viewer reads the thumbnail and the title together, in a fraction of a second, usually at
38
+ phone size. Every thumbnail follows the owner's brief:
39
+
40
+ - **Mildly tacky, attractive.** Loud on purpose: fat outlined type, a hard drop shadow, one
41
+ bright accent, a tilted sticker. But one hook and one visual, nothing else competing:
42
+ tacky is the energy, not clutter.
43
+ - **One clear hook.** At most 5 big words (32 characters) that make a viewer want the
44
+ answer: a tension, a surprise, a stake ("The bug nobody catches", "Your data is lying").
45
+ It **complements the upload title and never repeats it**: the title says what the video
46
+ is, the hook says why it is worth the click. Highlight the one word that carries the
47
+ punch. Never promise what the video does not deliver, the same rule as the title.
48
+ - **Sentence case.** Write the hook and the badge in sentence case: the first word capitalised,
49
+ the rest lower case except proper nouns, acronyms and product names ("Your data is lying",
50
+ "Why TypeScript lies"). The template renders text exactly as written, so never type them in
51
+ all capitals.
52
+ - **High contrast.** White type with a black stroke over a darkened wedge, and one accent
53
+ colour (default yellow `#FFD400`) picked to stand out against the still, not to match it.
54
+ - **A strong visual from the video itself.** A still rendered from one of this video's own
55
+ stage-5 scenes, at the beat that shows the idea at its most dramatic (the failure, the
56
+ reveal, the before and after), cropped with `focus` so its subject sits clear of the hook.
57
+ Never stock art, never a logo on its own.
58
+ - **Readable at small mobile size.** If the hook cannot be read in the 320x180 preview, it
59
+ is too long: cut words.
60
+
61
+ ### The series label
62
+
63
+ When the video belongs to a series or an intended playlist, a band across the top reads
64
+ **`<playlist name>-Part X, <title>`**: the first link of chronology a new viewer sees.
65
+
66
+ - **The playlist name comes only from the user**: the Stage 0 answer. Never guess it, never
67
+ default it, never derive it from the notes or the repo. Pass `--standalone`, with no
68
+ label, only when the plan has one video or the user explicitly said there is no
69
+ playlist; a blank Stage 0 answer on a series is not that.
70
+ - **Unset for a series means ask.** With neither `--playlist` nor `--standalone`, a plan of
71
+ more than one video exits **3**. Ask the user whether the series is published as a
72
+ playlist and under what name, then re-run with `--playlist "<name>"` or `--standalone`.
73
+ - **Part X** is computed by the tool: the video's 1-based position in
74
+ `plan.enriched.json`'s `videos` list, its index after curation's skips and merges. It is
75
+ not the raw `videoIndex`, which keeps the gaps curation left.
76
+ - **The title** is the final upload title, read from `title.txt`.
77
+
78
+ ### Pick the still
79
+
80
+ Choose the beat from the scenes you authored at stage 5, then render that instant:
81
+
82
+ ```bash
83
+ node ../explainer-video-from-coursework/tools/render.mjs --shots <t> --work out/<name>/<NN> \
84
+ --scene "src/scene-diagram.html?scene=<name>" --shot-dir out/<name>/<NN>/thumbnail
85
+ ```
86
+
87
+ `--shots` takes seconds of video time; pick one inside the segment's window in
88
+ `timeline.json`. Use the segment's own host (`scene-diagram`, `scene-svg` or `scene-code`).
89
+
90
+ ### Write `thumbnail.json`
91
+
92
+ Write `out/<name>/<NN>/thumbnail.json`:
93
+
94
+ ```json
95
+ {
96
+ "hook": "The bug nobody catches",
97
+ "highlight": "nobody",
98
+ "still": "thumbnail/t000042.png",
99
+ "focus": { "x": 0.7, "y": 0.45, "zoom": 1.3 },
100
+ "accent": "#FFD400",
101
+ "badge": "8 min"
102
+ }
103
+ ```
104
+
105
+ - `hook` (required) — at most 5 words and 32 characters; it must not repeat the upload title.
106
+ - `highlight` (optional) — a word of the hook, or a list of them, drawn in the accent colour.
107
+ - `still` (required) — the PNG from the step above, relative to the work dir.
108
+ - `focus` (optional) — the still's point to keep in view, `x` and `y` as fractions (0–1),
109
+ and a `zoom` of 1–4. The hook sits on the left, so frame the subject to the right.
110
+ - `accent` (optional) — a hex colour for the highlight, the label band, the badge and the bar.
111
+ - `badge` (optional) — a short tilted sticker, 1–16 characters ("8 min", "Fixed"), in sentence case like the hook.
112
+
113
+ ### Render and check
114
+
115
+ ```bash
116
+ node ../explainer-video-from-coursework/tools/thumbnail.mjs --work out/<name>/<NN> \
117
+ --script out/<name>/scripts/<NN>-*.json --plan out/<name>/plan.enriched.json \
118
+ --dest <destination> [--playlist "<name>" | --standalone]
119
+ ```
120
+
121
+ It validates `thumbnail.json`, adds the series label, photographs `src/thumbnail.html` at
122
+ 1280x720, checks the PNG and copies it into `--dest/<NN>-<slug>/`. It also writes
123
+ `out/<name>/<NN>/thumbnail.small.png`, a 320x180 copy for the phone-size look. It costs no
124
+ TTS and takes about a second, so iterate freely.
125
+
126
+ | Exit | Meaning |
127
+ |---|---|
128
+ | 0 | written: `thumbnail.png` in the work dir and in `--dest/<NN>-<slug>/` |
129
+ | 1 | a missing input or prerequisite: no `thumbnail.json`, no `title.txt` (run `/publish` first), a video not in the enriched plan, no Chromium |
130
+ | 2 | a check failed: the hook, the still, the crop, or the PNG (not 1280x720, not a PNG, over 2,000,000 bytes) |
131
+ | 3 | a series with no playlist decision: ask the user, never invent a name |
132
+
133
+ ### Look at it
134
+
135
+ The tool checks the shape of the thumbnail, never its appeal. **Vision gate:** read
136
+ `thumbnail.png` as an image. If that fails, you are on a text-only model: ask the user
137
+ whether to switch to a vision model or to skip the visual check. Never "look" blind.
138
+
139
+ Read both `thumbnail.png` and `thumbnail.small.png`, and check:
140
+
141
+ - the hook reads in the small copy, and the highlighted word is the punch;
142
+ - the hook does not cover the still's subject (move `focus`, or pick another still);
143
+ - the label, when there is one, is whole and names the right part and title;
144
+ - nothing is clipped at an edge, and the accent stands out against the still;
145
+ - it is loud and a little tacky, and still one idea, not a collage.
146
+
147
+ **Done when:** `thumbnail` exits 0, `thumbnail.png` sits in `--dest/<NN>-<slug>/` beside
148
+ `title.txt`, and both PNGs have been looked at, or the user skipped the visual check.
149
+
150
+ <!-- sync:codeflow-thumbnail -->
151
+ **Code-flow videos only.** A code-flow video's plan, `plan.json` or `plan.enriched.json`, carries
152
+ `"kind": "codebase"`, and its `forms` are `overview`, `flow` and `core`. A coursework plan has no `kind`; nothing
153
+ in this block applies to its videos. A code-flow video adds these rules at stage 6b:
154
+
155
+ - **Hook.** Name the flow in the viewer's terms, such as what a request does, what breaks or what a command runs
156
+ ("One click, three services"). Never use a module path, a file name or an identifier: `thumbnail` refuses a hook
157
+ word that is a path (`./x`, `dir/file.ext`, `a/b/c`), lowercase-first camelCase (`getUser`), snake_case, a dotted
158
+ module path (`pkg.mod.fn`), `::` or a call (`fn()`). Viewer words such as TypeScript, macOS, gRPC, I/O and CI/CD pass.
159
+ - **Still.** Take it from a diagram beat (the overview's system map, a flow's sequence, an eval's hand-off), never
160
+ from a code panel: a wall of code is unreadable texture at thumbnail size.
161
+ - **Secrets.** Never put a secret or a redacted value on the thumbnail.
162
+ <!-- /sync:codeflow-thumbnail -->
163
+
164
+ ## Maintenance
165
+
166
+ > If you modify this file, or the user asks to change this stage, tell the user that the
167
+ > main `explainer-video-from-coursework` skill's **"Stage 6b — /thumbnail"** section must be
168
+ > reconciled to match, and name that section in your reply.