@olegkoval/agent-skills 1.60.0 → 1.62.0

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 (223) hide show
  1. package/.github/copilot-instructions.md +2 -0
  2. package/.github/prompts/carousel-maker.prompt.md +102 -0
  3. package/.github/prompts/shorts-animator.prompt.md +103 -0
  4. package/.kiro/steering/carousel-maker.md +101 -0
  5. package/.kiro/steering/shorts-animator.md +102 -0
  6. package/.windsurf/rules/carousel-maker.md +100 -0
  7. package/.windsurf/rules/shorts-animator.md +101 -0
  8. package/README.md +4 -2
  9. package/adapters/claude/olko-creative/skills/carousel-maker/SKILL.md +112 -0
  10. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
  11. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
  12. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
  13. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
  14. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
  15. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
  16. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
  17. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
  18. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
  19. package/adapters/claude/olko-creative/skills/shorts-animator/SKILL.md +113 -0
  20. package/adapters/claude/olko-creative/skills/shorts-animator/references/kit.md +59 -0
  21. package/adapters/claude/olko-creative/skills/shorts-animator/references/looks.md +32 -0
  22. package/adapters/claude/olko-creative/skills/shorts-animator/references/writing.md +9 -0
  23. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
  24. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
  25. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
  26. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
  27. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
  28. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
  29. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
  30. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
  31. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
  32. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
  33. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
  34. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
  35. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
  36. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
  37. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
  38. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
  39. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
  40. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
  41. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
  42. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
  43. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
  44. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
  45. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
  46. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
  47. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
  48. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
  49. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
  50. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
  51. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
  52. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
  53. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
  54. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
  55. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
  56. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
  57. package/adapters/claude/olko-github-pr/skills/lekker-review/SKILL.md +30 -0
  58. package/adapters/claude/olko-github-pr/skills/lekker-review/references/context-gathering.md +3 -0
  59. package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/finding-state.mjs +207 -0
  60. package/adapters/codex/olko-creative/README.md +2 -0
  61. package/adapters/cursor/olko-creative/skills/carousel-maker/SKILL.md +112 -0
  62. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
  63. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
  64. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
  65. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
  66. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
  67. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
  68. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
  69. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
  70. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
  71. package/adapters/cursor/olko-creative/skills/shorts-animator/SKILL.md +113 -0
  72. package/adapters/cursor/olko-creative/skills/shorts-animator/references/kit.md +59 -0
  73. package/adapters/cursor/olko-creative/skills/shorts-animator/references/looks.md +32 -0
  74. package/adapters/cursor/olko-creative/skills/shorts-animator/references/writing.md +9 -0
  75. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
  76. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
  77. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
  78. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
  79. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
  80. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
  81. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
  82. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
  83. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
  84. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
  85. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
  86. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
  87. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
  88. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
  89. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
  90. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
  91. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
  92. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
  93. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
  94. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
  95. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
  96. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
  97. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
  98. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
  99. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
  100. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
  101. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
  102. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
  103. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
  104. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
  105. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
  106. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
  107. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
  108. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
  109. package/adapters/grok/olko-creative/skills/carousel-maker/SKILL.md +112 -0
  110. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
  111. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
  112. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
  113. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
  114. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
  115. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
  116. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
  117. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
  118. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
  119. package/adapters/grok/olko-creative/skills/shorts-animator/SKILL.md +113 -0
  120. package/adapters/grok/olko-creative/skills/shorts-animator/references/kit.md +59 -0
  121. package/adapters/grok/olko-creative/skills/shorts-animator/references/looks.md +32 -0
  122. package/adapters/grok/olko-creative/skills/shorts-animator/references/writing.md +9 -0
  123. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
  124. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
  125. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
  126. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
  127. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
  128. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
  129. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
  130. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
  131. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
  132. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
  133. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
  134. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
  135. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
  136. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
  137. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
  138. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
  139. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
  140. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
  141. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
  142. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
  143. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
  144. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
  145. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
  146. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
  147. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
  148. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
  149. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
  150. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
  151. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
  152. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
  153. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
  154. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
  155. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
  156. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
  157. package/adapters/hermes/olko-creative/README.md +2 -0
  158. package/catalog/skills.json +46 -0
  159. package/package.json +1 -1
  160. package/plugins/olko-apple-kit/.claude-plugin/plugin.json +1 -1
  161. package/plugins/olko-creative/.claude-plugin/plugin.json +1 -1
  162. package/plugins/olko-creative/skills/carousel-maker/SKILL.md +111 -0
  163. package/plugins/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
  164. package/plugins/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
  165. package/plugins/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
  166. package/plugins/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
  167. package/plugins/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
  168. package/plugins/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
  169. package/plugins/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
  170. package/plugins/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
  171. package/plugins/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
  172. package/plugins/olko-creative/skills/shorts-animator/SKILL.md +112 -0
  173. package/plugins/olko-creative/skills/shorts-animator/references/kit.md +59 -0
  174. package/plugins/olko-creative/skills/shorts-animator/references/looks.md +32 -0
  175. package/plugins/olko-creative/skills/shorts-animator/references/writing.md +9 -0
  176. package/plugins/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
  177. package/plugins/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
  178. package/plugins/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
  179. package/plugins/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
  180. package/plugins/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
  181. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
  182. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
  183. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
  184. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
  185. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
  186. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
  187. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
  188. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
  189. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
  190. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
  191. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
  192. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
  193. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
  194. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
  195. package/plugins/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
  196. package/plugins/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
  197. package/plugins/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
  198. package/plugins/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
  199. package/plugins/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
  200. package/plugins/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
  201. package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
  202. package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
  203. package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
  204. package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
  205. package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
  206. package/plugins/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
  207. package/plugins/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
  208. package/plugins/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
  209. package/plugins/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
  210. package/plugins/olko-garmin-kit/.claude-plugin/plugin.json +1 -1
  211. package/plugins/olko-git-tools/.claude-plugin/plugin.json +1 -1
  212. package/plugins/olko-github-pr/.claude-plugin/plugin.json +1 -1
  213. package/plugins/olko-github-pr/skills/lekker-review/SKILL.md +30 -0
  214. package/plugins/olko-github-pr/skills/lekker-review/references/context-gathering.md +3 -0
  215. package/plugins/olko-github-pr/skills/lekker-review/scripts/finding-state.mjs +207 -0
  216. package/plugins/olko-github-pr/skills/lekker-review/workflow.js +46 -3
  217. package/plugins/olko-obsidian/.claude-plugin/plugin.json +1 -1
  218. package/plugins/olko-product/.claude-plugin/plugin.json +1 -1
  219. package/plugins/olko-reflection/.claude-plugin/plugin.json +1 -1
  220. package/plugins/olko-release/.claude-plugin/plugin.json +1 -1
  221. package/plugins/olko-skill-meta/.claude-plugin/plugin.json +1 -1
  222. package/plugins/olko-web-ops/.claude-plugin/plugin.json +1 -1
  223. package/scripts/lib/catalog.mjs +2 -1
@@ -0,0 +1,87 @@
1
+ // Every process, browser and disk write the tool makes, kept out of the domain.
2
+ import { spawnSync, type SpawnSyncOptions } from "node:child_process";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { pathToFileURL } from "node:url";
7
+ import { H, W } from "../domain/layout.ts";
8
+ import type { Json } from "../domain/types.ts";
9
+
10
+ export function run(cmd: string, args: readonly string[], opts: SpawnSyncOptions = {}): string {
11
+ const r = spawnSync(cmd, args, { encoding: "utf8", maxBuffer: 1 << 26, timeout: 120_000, ...opts });
12
+ if (r.status !== 0) {
13
+ const out = String(r.stderr || r.stdout || r.error?.message || "");
14
+ throw new Error(`${path.basename(cmd)} ${args.slice(0, 2).join(" ")}... failed:\n${out.slice(-1500)}`);
15
+ }
16
+ return String(r.stdout ?? "");
17
+ }
18
+
19
+ export const has = (cmd: string): boolean => spawnSync(cmd, ["-version"], { stdio: "ignore" }).status === 0;
20
+ export const readJson = (f: string): unknown => JSON.parse(fs.readFileSync(f, "utf8"));
21
+ export const writeJson = (f: string, v: Json | object): void => fs.writeFileSync(f, JSON.stringify(v, null, 2) + "\n");
22
+ export const mkdir = (...parts: string[]): string => {
23
+ const d = path.join(...parts);
24
+ fs.mkdirSync(d, { recursive: true });
25
+ return d;
26
+ };
27
+ export const writeNew = (f: string, text: string): boolean => {
28
+ if (fs.existsSync(f)) return false;
29
+ fs.writeFileSync(f, text);
30
+ return true;
31
+ };
32
+
33
+ export type Chrome = { readonly bin: string; readonly shell: boolean };
34
+
35
+ const SHELL = process.platform === "win32" ? "chrome-headless-shell.exe" : "chrome-headless-shell";
36
+ const SYSTEM: Readonly<Record<string, readonly string[]>> = {
37
+ darwin: ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/Applications/Chromium.app/Contents/MacOS/Chromium", "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"],
38
+ linux: ["/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/usr/bin/chromium", "/usr/bin/chromium-browser"],
39
+ win32: [
40
+ `${process.env["PROGRAMFILES"]}\\Google\\Chrome\\Application\\chrome.exe`,
41
+ `${process.env["PROGRAMFILES(X86)"]}\\Google\\Chrome\\Application\\chrome.exe`,
42
+ `${process.env["PROGRAMFILES(X86)"]}\\Microsoft\\Edge\\Application\\msedge.exe`,
43
+ ],
44
+ };
45
+
46
+ const dirs = (d: string): readonly string[] =>
47
+ fs.existsSync(d) ? fs.readdirSync(d, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path.join(d, e.name)) : [];
48
+
49
+ /** The headless shell shorts-animator's doctor (via HyperFrames) downloads, newest first. */
50
+ const cachedShell = (): string | undefined =>
51
+ dirs(path.join(os.homedir(), ".cache/hyperframes/chrome/chrome-headless-shell"))
52
+ .toSorted()
53
+ .reverse()
54
+ .flatMap((v) => dirs(v).map((sub) => path.join(sub, SHELL)))
55
+ .find((f) => fs.existsSync(f));
56
+
57
+ export function findChrome(): Chrome | undefined {
58
+ const env = process.env.CHROME;
59
+ if (env && fs.existsSync(env)) return { bin: env, shell: /headless-shell/.test(env) };
60
+ const sys = (SYSTEM[process.platform] ?? []).find((c) => fs.existsSync(c));
61
+ if (sys) return { bin: sys, shell: false };
62
+ const shell = cachedShell();
63
+ return shell ? { bin: shell, shell: true } : undefined;
64
+ }
65
+
66
+ // One short-lived browser per call: no DevTools socket to manage, and a crash costs one slide.
67
+ const flags = (c: Chrome, height: number): readonly string[] => [
68
+ ...(c.shell ? [] : ["--headless"]), "--disable-gpu", "--hide-scrollbars", "--allow-file-access-from-files",
69
+ "--force-device-scale-factor=1", "--no-first-run", "--no-default-browser-check", `--window-size=${W},${height}`,
70
+ ];
71
+
72
+ export const url = (file: string): string => pathToFileURL(path.resolve(file)).href;
73
+
74
+ export const screenshot = (c: Chrome, html: string, png: string): void =>
75
+ void run(c.bin, [...flags(c, H), `--screenshot=${png}`, url(html)], { stdio: ["ignore", "ignore", "pipe"] });
76
+
77
+ /** The DOM after load, images and fonts included (the virtual time budget waits for them). */
78
+ export const dumpDom = (c: Chrome, html: string, height: number): string =>
79
+ run(c.bin, [...flags(c, height), "--virtual-time-budget=5000", "--dump-dom", url(html)]);
80
+
81
+ export const sheet = (pattern: string, count: number, out: string): boolean => {
82
+ if (!has("ffmpeg")) return false;
83
+ const cols = Math.min(count, 5);
84
+ run("ffmpeg", ["-v", "error", "-y", "-start_number", "1", "-framerate", "1", "-i", pattern,
85
+ "-vf", `scale=360:-2,tile=${cols}x${Math.ceil(count / cols)}:padding=8:color=white`, "-frames:v", "1", out]);
86
+ return true;
87
+ };
@@ -0,0 +1,4 @@
1
+ {
2
+ "private": true,
3
+ "type": "module"
4
+ }
@@ -0,0 +1,99 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { check, fitFindings, report, type Facts } from "../domain/check.ts";
4
+ import { contexts, esc, section } from "../domain/layout.ts";
5
+ import { artOf, parseBrand, parseDeck } from "../domain/schema.ts";
6
+ import type { Brand, Deck, Result } from "../domain/types.ts";
7
+
8
+ const value = <T>(r: Result<T>): T => {
9
+ if (!r.ok) throw new Error(r.errors.join("; "));
10
+ return r.value;
11
+ };
12
+
13
+ const BRAND: Brand = value(parseBrand({ name: "Slow Hours", handle: "@slowhours" }));
14
+ const RAW = {
15
+ title: "mornings",
16
+ slides: [
17
+ { kind: "cover", title: "Slow mornings, in 4 steps", body: "A calmer first hour.", art: "window" },
18
+ { kind: "point", title: "Wake with the light", body: "Open the curtains.", art: "light" },
19
+ { kind: "point", title: "Coffee, outside", art: "photos/mug.jpg" },
20
+ { kind: "cta", title: "Save this for tomorrow", follow: "Follow @slowhours" },
21
+ ],
22
+ };
23
+ const deck = (patch: object = {}): Deck => value(parseDeck({ ...RAW, ...patch }));
24
+ const facts = (d: Deck, library = ["window", "light"], files = ["photos/mug.jpg"]): Facts => ({ deck: d, library, files });
25
+ const msgs = (f: Facts, level: "error" | "warn"): readonly string[] => check(f).filter((x) => x.level === level).map((x) => `${x.where}: ${x.msg}`);
26
+
27
+ test("parseBrand fills default colours and fonts, and rejects a bad colour", () => {
28
+ assert.equal(BRAND.colors.accent, "#e0783a");
29
+ assert.match(BRAND.fonts.head, /Georgia/);
30
+ assert.equal(parseBrand({ name: "x", colors: { accent: "orange" } }).ok, false);
31
+ assert.equal(parseBrand({}).ok, false);
32
+ });
33
+
34
+ test("artOf tells a library drawing from a file from nothing", () => {
35
+ assert.deepEqual(artOf("window"), { kind: "library", name: "window" });
36
+ assert.deepEqual(artOf("photos/mug.jpg"), { kind: "file", file: "photos/mug.jpg" });
37
+ assert.deepEqual(artOf(undefined), { kind: "none" });
38
+ assert.deepEqual(artOf(""), { kind: "none" });
39
+ });
40
+
41
+ test("parseDeck defaults the kind to point and names every slide without a title", () => {
42
+ assert.equal(deck({ slides: [{ title: "a" }] }).slides[0]?.kind, "point");
43
+ const bad = parseDeck({ slides: [{ title: "a" }, {}, { kind: "cta" }] });
44
+ assert.equal(bad.ok, false);
45
+ assert.deepEqual(bad.ok ? [] : bad.errors, ["slides[1] needs a title", "slides[2] needs a title"]);
46
+ assert.equal(parseDeck({ slides: [] }).ok, false);
47
+ });
48
+
49
+ test("a well-shaped deck with its art present checks clean", () => {
50
+ assert.equal(report(check(facts(deck()))), "check: clean");
51
+ });
52
+
53
+ test("missing library drawings and files are errors", () => {
54
+ const errors = msgs(facts(deck(), ["window"], []), "error");
55
+ assert.ok(errors.some((m) => m.startsWith("slide 2") && m.includes('no drawing "light"')));
56
+ assert.ok(errors.some((m) => m.startsWith("slide 3") && m.includes("no file photos/mug.jpg")));
57
+ });
58
+
59
+ test("an unknown kind is an error; a deck without a cover or cta is warned", () => {
60
+ const d = deck({ slides: [{ kind: "quote", title: "a b" }, { kind: "point", title: "c d" }, { kind: "point", title: "e f" }] });
61
+ assert.ok(msgs(facts(d), "error").some((m) => m.includes('kind "quote"')));
62
+ const warns = msgs(facts(d), "warn");
63
+ assert.ok(warns.some((m) => m.startsWith("slide 1") && m.includes("cover")));
64
+ assert.ok(warns.some((m) => m.startsWith("slide 3") && m.includes("cta")));
65
+ });
66
+
67
+ test("long titles and repeated pictures are warned", () => {
68
+ const d = deck({ slides: [
69
+ { kind: "cover", title: "one two three four five six seven eight nine", art: "window" },
70
+ { kind: "point", title: "short", art: "window" },
71
+ { kind: "cta", title: "save" },
72
+ ] });
73
+ const warns = msgs(facts(d), "warn");
74
+ assert.ok(warns.some((m) => m.includes("title is 9 words")));
75
+ assert.ok(warns.some((m) => m.startsWith("slide 2") && m.includes("repeats the picture of slide 1")));
76
+ });
77
+
78
+ test("fitFindings turns the browser's measurements into errors", () => {
79
+ const f = fitFindings([{ slide: 1, overflow: false, missingArt: false }, { slide: 2, overflow: true, missingArt: true }]);
80
+ assert.deepEqual(f.map((x) => x.where), ["slide 2", "slide 2"]);
81
+ assert.ok(f.every((x) => x.level === "error"));
82
+ });
83
+
84
+ test("points are numbered among points only, and the footer counts every slide", () => {
85
+ const d = deck();
86
+ const ctx = contexts(BRAND, d.slides, ["a.jpg", "b.jpg", "c.jpg", undefined]);
87
+ assert.deepEqual(ctx.map((c) => c.point), [0, 1, 2, 2]);
88
+ const html = ctx.map((c, i) => (d.slides[i] ? section(d.slides[i], c) : ""));
89
+ assert.match(html[2] ?? "", /<div class="n">02<\/div>/);
90
+ assert.match(html[0] ?? "", /swipe/);
91
+ assert.match(html[3] ?? "", /4\/4/);
92
+ assert.match(html[3] ?? "", /class="slide cta noart"/);
93
+ });
94
+
95
+ test("copy is escaped into the HTML", () => {
96
+ assert.equal(esc(`<b>"x" & y</b>`), "&lt;b&gt;&quot;x&quot; &amp; y&lt;/b&gt;");
97
+ const html = section({ kind: "point", title: "<script>", art: { kind: "none" } }, { brand: BRAND, n: 2, total: 3, point: 1 });
98
+ assert.ok(!html.includes("<script>"));
99
+ });
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2023",
4
+ "lib": ["es2023"],
5
+ "module": "nodenext",
6
+ "moduleResolution": "nodenext",
7
+ "types": ["node"],
8
+ "strict": true,
9
+ "noEmit": true,
10
+ "allowImportingTsExtensions": true,
11
+ "erasableSyntaxOnly": true,
12
+ "verbatimModuleSyntax": true,
13
+ "skipLibCheck": true
14
+ },
15
+ "include": ["*.ts", "domain", "edge", "test"]
16
+ }
@@ -0,0 +1,113 @@
1
+ ---
2
+ name: shorts-animator
3
+ description: Use when someone wants a narrated, painted or hand-drawn animated short (TikTok, Reels, YouTube Shorts) from a script or topic and a look described in words. Every frame is drawn in code and GPU-painted, voiced locally, captioned and scored into an MP4; no image models or stock.
4
+ license: MIT
5
+ allowed-tools: Bash, Read, Write, Edit
6
+ compatibility: Codex, Claude Code, Hermes Agent, Cursor, GitHub Copilot, Windsurf, Kiro, and other Agent Skills compatible tools. Needs Node 22.18+, ffmpeg and Chrome; the narrator installs Python and its voice model on first use.
7
+ metadata:
8
+ targets: [_source-only]
9
+ author: Oleg Koval
10
+ tags:
11
+ - video
12
+ - animation
13
+ - shorts
14
+ - narration
15
+ - canvas
16
+ ---
17
+ <!-- Generated by scripts/build-adapters.sh. Do not edit directly. -->
18
+
19
+ # Shorts Animator
20
+
21
+ A script and a look in, an animated short out. You write the lines and draw every shot as canvas code; the
22
+ tool paints it on the GPU on twos, narrates it with a local Kokoro voice, captions it, scores it and
23
+ encodes the MP4. Nothing is filmed, generated by an image model, or licensed.
24
+
25
+ `SA` = `node <this skill's folder>/scripts/sa.ts`. Write the full command each time. One folder per short:
26
+ `WORK=./<slug>-short`. First run on a machine: `SA doctor` (checks Node, ffmpeg, Chrome, sets up the voice,
27
+ renders a 3-second test).
28
+
29
+ ## 1. Decide, do not interrogate
30
+
31
+ Decide the lines, shots, look, voice and music yourself. Ask one question only if the subject is unclear.
32
+ A pasted script is used word for word. A topic: write it per `references/writing.md`.
33
+
34
+ | they say | look |
35
+ |---|---|
36
+ | oil, painterly, Van Gogh, swirly, "like the faith shorts" | `oil` |
37
+ | watercolour, soft, dreamy, children's book | `watercolor` |
38
+ | crayon, pastel, chalk, for kids | `pastel` |
39
+ | ink, comic, graphic novel, line art | `ink` |
40
+ | flat, storybook, gouache, poster, calm | `gouache` |
41
+
42
+ Custom look: `"look": { "base": "oil", "brush": 16, "palette": {...} }`. Read `references/looks.md` only
43
+ when overriding numbers. Voice `bm_george` (default, scripture and quotes), `am_michael`, `bf_emma`
44
+ (stories), `af_heart` (kids); `speed` 0.85 to 0.95 reads calm. Their recording: `"voice": { "file":
45
+ "assets/voice.m4a" }` with `say` lines matching it in order. `"voice": "none"` for music only. Music
46
+ `mood`: `tender`, `hopeful`, `night`, `wonder`, `playful`, `none`, or a track path. `SA looks` lists all.
47
+
48
+ ## 2. Write the plan and the drawings
49
+
50
+ ```bash
51
+ SA new "$WORK" --look oil # short.json + scenes.js + assets/
52
+ ```
53
+
54
+ `short.json` lines: `{ "say": "...", "scene": "meadow" | ["a", "b"], "move": "push", "hold": 0.8 }`.
55
+ - A list of scenes cuts on the caption chunks. Cut often: a new picture every 1.5 to 3 s.
56
+ - `move`: push, pull, pan-left, pan-right, rise, fall, drift, still. `transition`: cut, fade, dip (rarely).
57
+ - `caption` shows different words; `/` forces a break. `captions.style`: box, plain, none.
58
+ - `format`: 9:16 (default), 16:9, 1:1, 4:5. `twos`: 2 (hand-drawn), 1 (smooth).
59
+
60
+ `scenes.js`: `window.SCENES = { "meadow": (k) => { k.sky({...}); k.person({...}); } }`. Read
61
+ `references/kit.md` before writing scenes. Draw like a painter:
62
+ - Big simple shapes, strong value contrast, one subject. The paint pass adds texture; draw nothing fussy.
63
+ - Keep faces out of the caption band (about 77% down).
64
+ - Every shot different: place, scale, day and night, warm and cool. Match the words.
65
+ - Small motion: `pose: "walk"`, `dx`, rain, glints. `k.t` seconds, `k.p` progress 0 to 1.
66
+ - No helper for it (a boat, an animal)? Write a small function with `k.g`, flat shapes in the palette.
67
+
68
+ ## 3. Check, then look (budgeted)
69
+
70
+ ```bash
71
+ SA check "$WORK" # no browser, about a second: missing scenes, throwing scenes, unknown kit calls,
72
+ # figures under the captions, line and total length
73
+ SA frames "$WORK" # narrates (cached), one painted frame per shot -> frames/sheet.jpg
74
+ SA still "$WORK" 7 # one shot full size, only when the sheet shows a problem you cannot place
75
+ ```
76
+
77
+ Fix every `ERROR` from `check` before spending a browser run. Then read `frames/sheet.jpg` once. Fix muddy
78
+ or empty shots, a figure lost in its background (contrast, `rim`), look-alike shots, a red "no scene"
79
+ frame, and every `⚠ the page said` line. **Budget: at most two frames rounds before the first render.**
80
+ Images are the expensive part of this loop; do not reopen a sheet you have already judged.
81
+
82
+ ## 4. Render and deliver
83
+
84
+ ```bash
85
+ SA render "$WORK" # about a minute for 40 s -> <title>.mp4, renders/vN.mp4 (+ its short.json), qa/sheet.jpg
86
+ ```
87
+
88
+ Read `qa/sheet.jpg` only if the render printed a warning or they report a problem. Tell them in two
89
+ lines what it is (look, length, shots) and: "Tell me what to change, one sentence is enough."
90
+
91
+ ## 5. One-sentence changes
92
+
93
+ Smallest change, `SA check`, render, show. `SA set "$WORK" voice.speed=0.85 music.mood=night` edits
94
+ short.json (JSON values; plain text otherwise) and refuses an invalid result.
95
+
96
+ | they say | change |
97
+ |---|---|
98
+ | more painterly, softer | `look` or its numbers (brush, swirl, wash) |
99
+ | slower, faster | `voice.speed`, `pause` |
100
+ | a woman's voice, American | `voice.id` |
101
+ | no music, more emotional | `music.mood` |
102
+ | bigger captions, no box | `captions.size` 52, `captions.style` plain |
103
+ | for YouTube | `format` 16:9, redraw shots whose layout breaks |
104
+ | change shot 4 | that scene function only |
105
+
106
+ ## When something goes wrong
107
+
108
+ | symptom | fix |
109
+ |---|---|
110
+ | no Chrome | install Chrome, or `SA doctor` fetches a headless one |
111
+ | "painting on the CPU" | works, about 10x slower |
112
+ | narrator setup fails | needs Python 3.10+ or `uv`; or use `"voice": "none"` or their recording |
113
+ | voice timing off with their recording | `say` lines must match its words, in order |
@@ -0,0 +1,59 @@
1
+ # The drawing kit
2
+
3
+ Every scene is `(k) => { ... }`. Positions are fractions of the frame (x 0→1 left→right, y 0→1 top→bottom);
4
+ sizes in px are for a 1920-tall frame and scale with it (`k.u`). Draw back to front: sky, far, middle, near.
5
+
6
+ | in `k` | what |
7
+ |---|---|
8
+ | `k.g` | the 2D canvas context (camera already applied): draw anything the helpers don't cover |
9
+ | `k.W`, `k.H`, `k.u` | frame size, and the scale (H/1920) |
10
+ | `k.X(x)`, `k.Y(y)` | fraction → pixels |
11
+ | `k.t`, `k.p` | seconds into the shot (on twos), progress 0→1 |
12
+ | `k.r()` | random, the same on every drawing of the shot |
13
+ | `k.rng(seed)`, `k.noise1(x)` | a seeded random source; smooth 1D noise |
14
+ | `k.pal`, `k.beat` | the short's palette; this shot's beat (scene, line, move) |
15
+ | `k.mix(a, b, t)`, `k.alpha(c, a)` | colour helpers |
16
+ | `IMAGES.name` | an image from `assets/` (a logo, a reference), for `k.g.drawImage` |
17
+
18
+ ## Sky and light
19
+ - `k.sky({ stops: [top, …, bottom], y0, y1 })`: a gradient
20
+ - `k.clouds({ y, band, n, size, color, shade, drift, seed })`: small cumulus in a band, drifting
21
+ - `k.sun({ x, y, r, color, glow, glowSize, glowAlpha })`: sun, moon, a bright bulb
22
+ - `k.glow({ x, y, r, color, a })`: any soft light (windows, lanterns, a halo)
23
+ - `k.stars({ n, y1 })`, `k.motes({ n, y0, y1, color })` (fireflies, embers), `k.rain({ n, a, slant })`
24
+ - `k.rays({ x, y, angle, spread, n, a, width })`: god rays through trees or clouds
25
+ - `k.haze({ y, h, color, a })`: air between planes
26
+
27
+ ## Land and water
28
+ - `k.ridge({ y, amp, freq, color, top, shape, seed })`: filled ridge: `soft` hills, `peaks` mountains,
29
+ `pines` a conifer line, `bush` a broadleaf line, `rocky`
30
+ - `k.ground({ y, color, far, stripes, stripe, flowers })`: the ground plane, mown stripes, flowers
31
+ - `k.path({ x, y, near, far, color, bend })`: a road to the horizon
32
+ - `k.water({ y0, y1, color, deep, glint, glints })`: lake or sea with moving glints
33
+ - `k.waterfall({ x, w, y0, y1 })`
34
+ - `k.shape({ pts: [[x,y],…], color, smooth })`: any shape: a cliff, a giant tree trunk, an island
35
+
36
+ ## Plants
37
+ - `k.tree({ x, y, h, kind, crown, trunk, fruit })`: kind `round` `pine` `autumn` `bare` `palm` `column`
38
+ - `k.trees({ n, y, h, x0, x1, kind, crown })`: a row
39
+ - `k.foliage({ side, x, depth, y0, y1, color, light })`: leaves hanging into frame from a side
40
+ - `k.trunks({ n, color, light, width, x0, x1 })`: inside a forest
41
+
42
+ ## Built things
43
+ - `k.house({ x, y, w, h, wall, roof, window, lit, floors, aFrame })`
44
+ - `k.city({ y, h, color, lit, litColor, density, n })`: skyline, windows lit at night
45
+ - `k.pagoda({ x, y, h, color })`, `k.pole({ x, y, h })`, `k.lamp({ x, y, h, light })`
46
+ - `k.block({ x0, y0, x1, y1, color, edge })`: walls, doorways, a lit edge
47
+ - `k.heart({ x, y, s, color })`
48
+
49
+ ## People
50
+ `k.person({ x, y, h, pose, facing, hair, skin, hairColor, top, bottom, shoes, robe, rim, silhouette, walk, dx, flip, build })`
51
+ - feet at (x, y), `h` = height as a fraction of the frame (0.25 is a full figure in a landscape)
52
+ - `pose`: `stand` `walk` `cheer` `open` `wave` `point` `reach` `pray` `hug` `sit` `kneel` `hold`
53
+ - `facing`: `front` | `back` (faces are left blank on purpose: it's the style, and it reads at any size)
54
+ - `hair`: `short` `long` `curly` `afro` `bun` `hood` `hat` `cap` `bald`
55
+ - `robe: true`: a long garment (biblical, monks, a dress)
56
+ - `silhouette: "#111"`: one colour (backlit, sunset); `rim: "#9db6f0"`: a lit outline (night)
57
+ - `dx`: moves over the shot (0.05 = 5% of the width); `walk`: steps per second
58
+ - `k.people([{...}, {...}])`: several `person` calls in one
59
+ - Two people walking together read as a relationship; a tiny figure in a huge landscape reads as awe or loneliness.
@@ -0,0 +1,32 @@
1
+ # Looks: from words to numbers
2
+
3
+ A look is a JSON object. Start from a shipped one (`"base": "oil"`) and override.
4
+
5
+ | key | what it does | range |
6
+ |---|---|---|
7
+ | `mode` | `strokes` (paint/pastel), `wash` (watercolour/ink), `flat` | |
8
+ | `brush` | stroke size in px at 1920 tall | 5 fine … 11 oil … 18 bold |
9
+ | `length` | stroke length ÷ width | 2 dabs … 4.6 hatching |
10
+ | `swirl` | strokes follow a swirl (1) or the shapes (0) | 0 … 1 |
11
+ | `curve` | strokes bend | 0 … 1 |
12
+ | `bristle` | streaks inside a stroke, broken dry ends | 0 … 1 |
13
+ | `impasto` | thick paint catching light | 0 … 1 |
14
+ | `hueJitter` `valueJitter` `satJitter` | colour variation stroke to stroke | 0 … 0.4 |
15
+ | `respect` | big strokes stop at edges (crisper subjects) | 0 … 1 |
16
+ | `boil` | share of strokes redrawn every drawing (the shimmer) | 0 still … 1 lively |
17
+ | `wash` `bleed` | watercolour pooling, blooms, granulation; how far colour runs | 0 … 1 |
18
+ | `outline` `ink` | hand-inked lines, their colour | 0 … 1 |
19
+ | `paper` `paperColor` `grain` | canvas/paper texture, grain | 0 … 1.6 |
20
+ | `posterize` | flat colour bands (screen print, retro) | 0 off, 4 … 8 |
21
+ | `saturation` `contrast` `warmth` | grade | ~0.8 … 1.3 |
22
+ | `vignette` `bloom` | dark corners, glow from lights | 0 … 0.6 |
23
+ | `captions` | `{ style, size, y, box, color, font, case }` | |
24
+
25
+ Words → moves:
26
+ - "thicker / bolder paint" → brush +4, impasto +0.2 · "finer" → brush −3
27
+ - "swirly / Van Gogh" → swirl 0.9, curve 1, hueJitter 0.25 · "calmer" → swirl 0.3, boil 0.2
28
+ - "dreamy / soft" → watercolor, bleed 1 · "crisp" → respect 1, boil 0.2
29
+ - "vintage / old film" → saturation 0.8, warmth 0.5, grain 1, vignette 0.5
30
+ - "comic / graphic novel" → ink, posterize 6 · "screen print / retro poster" → gouache, posterize 5
31
+ - "night is too dark" → contrast 0.95 and lighten the palette, not the look
32
+ - "for kids" → pastel or gouache, saturation 1.1, warm palette, playful music
@@ -0,0 +1,9 @@
1
+ # Writing the lines
2
+
3
+ - One idea per line, 4 to 12 words. Read aloud it should take 1.5 to 4 seconds.
4
+ - Open on a hook that makes the second line necessary ("You have heard that it was said,").
5
+ - A turn two-thirds in ("But…", "And yet…", "Nobody ever…").
6
+ - Land the last line; give it `hold: 0.8` and a final wide or calm shot.
7
+ - Scripture, quotes and poems: use a public-domain translation (KJV, WEB) or their own words; keep it exact.
8
+ - Never invent a quote and attribute it to a real person.
9
+ - 20 to 45 seconds is the sweet spot for Shorts, Reels and TikTok; 60 s at most.