@olegkoval/agent-skills 1.59.3 → 1.61.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 (231) hide show
  1. package/.github/copilot-instructions.md +2 -0
  2. package/.github/prompts/carousel-maker.prompt.md +102 -0
  3. package/.github/prompts/self-critique.prompt.md +167 -43
  4. package/.github/prompts/shorts-animator.prompt.md +103 -0
  5. package/.kiro/steering/carousel-maker.md +101 -0
  6. package/.kiro/steering/shorts-animator.md +102 -0
  7. package/.windsurf/rules/carousel-maker.md +100 -0
  8. package/.windsurf/rules/shorts-animator.md +101 -0
  9. package/README.md +5 -3
  10. package/adapters/claude/olko-creative/skills/carousel-maker/SKILL.md +112 -0
  11. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
  12. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
  13. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
  14. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
  15. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
  16. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
  17. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
  18. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
  19. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
  20. package/adapters/claude/olko-creative/skills/shorts-animator/SKILL.md +113 -0
  21. package/adapters/claude/olko-creative/skills/shorts-animator/references/kit.md +59 -0
  22. package/adapters/claude/olko-creative/skills/shorts-animator/references/looks.md +32 -0
  23. package/adapters/claude/olko-creative/skills/shorts-animator/references/writing.md +9 -0
  24. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
  25. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
  26. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
  27. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
  28. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
  29. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
  30. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
  31. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
  32. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
  33. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
  34. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
  35. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
  36. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
  37. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
  38. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
  39. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
  40. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
  41. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
  42. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
  43. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
  44. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
  45. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
  46. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
  47. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
  48. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
  49. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
  50. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
  51. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
  52. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
  53. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
  54. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
  55. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
  56. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
  57. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
  58. package/adapters/claude/olko-reflection/skills/self-critique/SKILL.md +167 -43
  59. package/adapters/claude/olko-reflection/skills/self-critique/scripts/claim-ledger.mjs +159 -0
  60. package/adapters/claude/olko-reflection/skills/self-critique/scripts/rule-store.mjs +193 -0
  61. package/adapters/codex/olko-creative/README.md +2 -0
  62. package/adapters/cursor/olko-creative/skills/carousel-maker/SKILL.md +112 -0
  63. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
  64. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
  65. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
  66. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
  67. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
  68. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
  69. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
  70. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
  71. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
  72. package/adapters/cursor/olko-creative/skills/shorts-animator/SKILL.md +113 -0
  73. package/adapters/cursor/olko-creative/skills/shorts-animator/references/kit.md +59 -0
  74. package/adapters/cursor/olko-creative/skills/shorts-animator/references/looks.md +32 -0
  75. package/adapters/cursor/olko-creative/skills/shorts-animator/references/writing.md +9 -0
  76. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
  77. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
  78. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
  79. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
  80. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
  81. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
  82. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
  83. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
  84. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
  85. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
  86. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
  87. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
  88. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
  89. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
  90. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
  91. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
  92. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
  93. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
  94. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
  95. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
  96. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
  97. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
  98. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
  99. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
  100. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
  101. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
  102. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
  103. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
  104. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
  105. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
  106. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
  107. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
  108. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
  109. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
  110. package/adapters/cursor/olko-reflection/skills/self-critique/SKILL.md +167 -43
  111. package/adapters/cursor/olko-reflection/skills/self-critique/scripts/claim-ledger.mjs +159 -0
  112. package/adapters/cursor/olko-reflection/skills/self-critique/scripts/rule-store.mjs +193 -0
  113. package/adapters/grok/olko-creative/skills/carousel-maker/SKILL.md +112 -0
  114. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
  115. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
  116. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
  117. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
  118. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
  119. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
  120. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
  121. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
  122. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
  123. package/adapters/grok/olko-creative/skills/shorts-animator/SKILL.md +113 -0
  124. package/adapters/grok/olko-creative/skills/shorts-animator/references/kit.md +59 -0
  125. package/adapters/grok/olko-creative/skills/shorts-animator/references/looks.md +32 -0
  126. package/adapters/grok/olko-creative/skills/shorts-animator/references/writing.md +9 -0
  127. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
  128. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
  129. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
  130. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
  131. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
  132. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
  133. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
  134. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
  135. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
  136. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
  137. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
  138. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
  139. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
  140. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
  141. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
  142. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
  143. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
  144. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
  145. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
  146. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
  147. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
  148. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
  149. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
  150. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
  151. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
  152. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
  153. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
  154. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
  155. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
  156. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
  157. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
  158. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
  159. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
  160. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
  161. package/adapters/grok/olko-reflection/skills/self-critique/SKILL.md +167 -43
  162. package/adapters/grok/olko-reflection/skills/self-critique/scripts/claim-ledger.mjs +159 -0
  163. package/adapters/grok/olko-reflection/skills/self-critique/scripts/rule-store.mjs +193 -0
  164. package/adapters/hermes/olko-creative/README.md +2 -0
  165. package/catalog/skills.json +47 -1
  166. package/docs/superpowers/plans/2026-10-27-self-critique-v2.md +306 -0
  167. package/docs/superpowers/specs/2026-10-27-self-critique-v2-spec.md +104 -0
  168. package/package.json +1 -1
  169. package/plugins/olko-apple-kit/.claude-plugin/plugin.json +1 -1
  170. package/plugins/olko-creative/.claude-plugin/plugin.json +1 -1
  171. package/plugins/olko-creative/skills/carousel-maker/SKILL.md +111 -0
  172. package/plugins/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
  173. package/plugins/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
  174. package/plugins/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
  175. package/plugins/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
  176. package/plugins/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
  177. package/plugins/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
  178. package/plugins/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
  179. package/plugins/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
  180. package/plugins/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
  181. package/plugins/olko-creative/skills/shorts-animator/SKILL.md +112 -0
  182. package/plugins/olko-creative/skills/shorts-animator/references/kit.md +59 -0
  183. package/plugins/olko-creative/skills/shorts-animator/references/looks.md +32 -0
  184. package/plugins/olko-creative/skills/shorts-animator/references/writing.md +9 -0
  185. package/plugins/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
  186. package/plugins/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
  187. package/plugins/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
  188. package/plugins/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
  189. package/plugins/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
  190. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
  191. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
  192. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
  193. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
  194. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
  195. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
  196. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
  197. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
  198. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
  199. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
  200. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
  201. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
  202. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
  203. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
  204. package/plugins/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
  205. package/plugins/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
  206. package/plugins/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
  207. package/plugins/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
  208. package/plugins/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
  209. package/plugins/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
  210. package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
  211. package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
  212. package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
  213. package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
  214. package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
  215. package/plugins/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
  216. package/plugins/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
  217. package/plugins/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
  218. package/plugins/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
  219. package/plugins/olko-garmin-kit/.claude-plugin/plugin.json +1 -1
  220. package/plugins/olko-git-tools/.claude-plugin/plugin.json +1 -1
  221. package/plugins/olko-github-pr/.claude-plugin/plugin.json +1 -1
  222. package/plugins/olko-obsidian/.claude-plugin/plugin.json +1 -1
  223. package/plugins/olko-product/.claude-plugin/plugin.json +1 -1
  224. package/plugins/olko-reflection/.claude-plugin/plugin.json +1 -1
  225. package/plugins/olko-reflection/skills/self-critique/SKILL.md +167 -43
  226. package/plugins/olko-reflection/skills/self-critique/scripts/claim-ledger.mjs +159 -0
  227. package/plugins/olko-reflection/skills/self-critique/scripts/rule-store.mjs +193 -0
  228. package/plugins/olko-release/.claude-plugin/plugin.json +1 -1
  229. package/plugins/olko-skill-meta/.claude-plugin/plugin.json +1 -1
  230. package/plugins/olko-web-ops/.claude-plugin/plugin.json +1 -1
  231. package/scripts/lib/catalog.mjs +2 -1
@@ -0,0 +1,268 @@
1
+ /*
2
+ * THE PAINT PASS: a flat drawing in, a painted frame out.
3
+ *
4
+ * Every scene is first drawn flat (kit.js): clean shapes, no texture. This
5
+ * pass repaints it on the GPU in the short's look. "strokes" lays three
6
+ * layers of brush strokes (big, medium, small), each stroke one colour picked
7
+ * from the drawing at its centre, turned along the shapes and a slow swirl,
8
+ * with bristle streaks and a little impasto light. Big strokes stop at edges
9
+ * like a careful painter; the small ones finish the edges ragged. "wash" is
10
+ * watercolour: soft bleed, pigment pooled at the edges, granulation, paper.
11
+ * "flat" keeps the shapes and only adds the paper, grain and grade. Any mode
12
+ * can add ink lines and posterize.
13
+ *
14
+ * Every number lives in the look (looks/*.json, references/looks.md), so a
15
+ * style described in words becomes a look file, and the same scene can be
16
+ * painted any way.
17
+ *
18
+ * Frame by frame: `tick` is the drawing number. With boil > 0, that share of
19
+ * strokes is laid again on every drawing, so the paint shimmers the way
20
+ * hand-made animation does.
21
+ */
22
+ (function () {
23
+ const VS = `#version 300 es
24
+ in vec2 p; void main(){ gl_Position = vec4(p, 0., 1.); }`;
25
+
26
+ const FS = `#version 300 es
27
+ precision highp float;
28
+ uniform sampler2D uImg;
29
+ uniform vec2 uRes;
30
+ uniform float uS; // px scale: frame height / 1920
31
+ uniform float uTick;
32
+ uniform float uMode; // 0 strokes, 1 wash, 2 flat
33
+ uniform float uBrush, uLen, uLayers, uSwirl, uFlow, uBristle, uImpasto, uCurve;
34
+ uniform float uHueJ, uValJ, uSatJ, uRespect, uBoil;
35
+ uniform float uPaper, uGrain, uWash, uOutline, uPoster, uSat, uContrast, uWarm, uVig, uBloom, uBleed;
36
+ uniform vec3 uInk, uPaperCol;
37
+ out vec4 o;
38
+
39
+ vec3 hash32(vec2 p){ vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973)); p3 += dot(p3, p3.yxz + 33.33); return fract((p3.xxy + p3.yzz) * p3.zyx); }
40
+ float hash12(vec2 p){ vec3 p3 = fract(vec3(p.xyx) * .1031); p3 += dot(p3, p3.yzx + 33.33); return fract((p3.x + p3.y) * p3.z); }
41
+ float vnoise(vec2 p){ vec2 i = floor(p), f = fract(p); vec2 u = f*f*(3. - 2.*f);
42
+ return mix(mix(hash12(i), hash12(i + vec2(1,0)), u.x), mix(hash12(i + vec2(0,1)), hash12(i + vec2(1,1)), u.x), u.y); }
43
+ float fbm(vec2 p){ float s = 0., a = .5; for (int i = 0; i < 4; i++){ s += a*vnoise(p); p = p*2.03 + 7.1; a *= .5; } return s; }
44
+
45
+ vec3 img(vec2 px, float lod){ return textureLod(uImg, px / uRes, lod).rgb; }
46
+ float lum(vec3 c){ return dot(c, vec3(.299, .587, .114)); }
47
+
48
+ vec3 hueShift(vec3 c, float a){
49
+ const mat3 toYIQ = mat3(.299, .596, .211, .587, -.274, -.523, .114, -.322, .312);
50
+ const mat3 toRGB = mat3(1., 1., 1., .956, -.272, -1.106, .621, -.647, 1.703);
51
+ vec3 y = toYIQ * c; float h = atan(y.z, y.y) + a, ch = length(y.yz);
52
+ return toRGB * vec3(y.x, ch*cos(h), ch*sin(h));
53
+ }
54
+
55
+ vec2 flowDir(vec2 c){
56
+ float e = 7.*uS;
57
+ float gx = lum(img(c + vec2(e, 0.), 3.)) - lum(img(c - vec2(e, 0.), 3.));
58
+ float gy = lum(img(c + vec2(0., e), 3.)) - lum(img(c - vec2(0., e), 3.));
59
+ float ang = fbm(c / (uFlow*uS) + vec2(uTick*.013*uBoil, 0.)) * 6.2832 * 1.7;
60
+ vec2 nd = vec2(cos(ang), sin(ang));
61
+ vec2 g = vec2(gx, gy);
62
+ float gl = length(g);
63
+ if (gl < 1e-4) return nd;
64
+ vec2 ed = vec2(-g.y, g.x) / gl;
65
+ if (dot(ed, nd) < 0.) ed = -ed;
66
+ float w = clamp(gl*9., 0., 1.) * (1. - .75*uSwirl);
67
+ return normalize(mix(nd, ed, w));
68
+ }
69
+
70
+ // One layer of strokes of a given size. rgb + coverage.
71
+ vec4 strokes(vec2 px, float size, float lseed, float respect){
72
+ vec2 cell = floor(px / size);
73
+ float bestZ = -1.;
74
+ vec4 res = vec4(0.);
75
+ vec3 here = img(px, 0.);
76
+ for (int j = -2; j <= 2; j++) for (int i = -2; i <= 2; i++){
77
+ vec2 c = cell + vec2(float(i), float(j));
78
+ float boil = hash12(c + lseed*3.1) < uBoil ? uTick : 0.;
79
+ vec3 h = hash32(c*1.37 + lseed + boil*.618);
80
+ vec2 ctr = (c + .1 + .8*h.xy) * size;
81
+ vec2 d = px - ctr;
82
+ float W = size*(.34 + .22*h.z);
83
+ float Lh = size*uLen*.5*(.7 + .6*h.y);
84
+ if (dot(d, d) > (Lh + W)*(Lh + W)) continue;
85
+ float z = hash12(c + lseed + 19.7 + boil);
86
+ if (z < bestZ) continue;
87
+ vec2 dir = flowDir(ctr);
88
+ float u = dot(d, dir), v = dot(d, vec2(-dir.y, dir.x));
89
+ v -= (h.x - .5) * uCurve * u*u / Lh;
90
+ float un = u / Lh;
91
+ if (abs(un) > 1.) continue;
92
+ float taper = sqrt(max(0., 1. - pow(abs(un), 2.4)));
93
+ float wv = W*taper*(.82 + .36*vnoise(vec2(u/(6.*uS), h.z*40.)));
94
+ float edge = abs(v) - wv;
95
+ if (edge > 0.) continue;
96
+ vec3 col = img(ctr, .6);
97
+ // a careful painter stops a big stroke at an edge
98
+ float a = clamp(-edge / (1.3*uS), 0., 1.);
99
+ a *= 1. - respect*smoothstep(.10, .28, distance(here, col));
100
+ if (a <= .02) continue;
101
+ float br = vnoise(vec2(v/(1.7*uS) + h.x*91., un*.7 + h.y*13.));
102
+ a *= mix(1., step(.38, br), smoothstep(.55, 1., abs(un))*uBristle);
103
+ if (a <= .02) continue;
104
+ // colour of this stroke: the drawing's, nudged
105
+ col = hueShift(col, (h.x - .5)*uHueJ);
106
+ col *= 1. + (h.y - .5)*uValJ;
107
+ float l = lum(col); col = mix(vec3(l), col, 1. + (h.z - .5)*uSatJ);
108
+ // bristle streaks along the stroke, and the ridge catching light
109
+ col *= 1. + (br - .5)*uBristle*.32;
110
+ float side = v / max(wv, 1e-3);
111
+ float lit = dot(vec2(-dir.y, dir.x), normalize(vec2(-.6, -.8)));
112
+ col *= 1. + uImpasto*(.16*side*lit + .08*(1. - side*side) - .05);
113
+ bestZ = z;
114
+ res = vec4(col, a);
115
+ }
116
+ return res;
117
+ }
118
+
119
+ void main(){
120
+ vec2 px = vec2(gl_FragCoord.x, uRes.y - gl_FragCoord.y);
121
+ vec3 col;
122
+ if (uMode < .5){
123
+ col = img(px + (vec2(fbm(px/(9.*uS)), fbm(px/(9.*uS) + 5.)) - .5)*4.*uS, 1.5);
124
+ float b = uBrush*uS;
125
+ for (int k = 0; k < 3; k++){
126
+ float fk = float(k);
127
+ if (fk >= uLayers) break;
128
+ float size = b * (fk == 0. ? 2.3 : fk == 1. ? 1.35 : .78);
129
+ float respect = uRespect * (fk == 0. ? 1. : fk == 1. ? .8 : .35);
130
+ vec4 s = strokes(px, size, fk*17.31 + 3., respect);
131
+ col = mix(col, s.rgb, s.a);
132
+ }
133
+ } else if (uMode < 1.5){
134
+ // the wash moves a little each drawing (boil), and bleeds past the line
135
+ float bt = floor(uTick*uBoil + .5)*.37;
136
+ vec2 q = px + (vec2(fbm(px/(16.*uS) + bt), fbm(px/(16.*uS) + 9. + bt)) - .5)*uBleed*22.*uS;
137
+ col = img(q, 1.4 + uBleed);
138
+ // pigment pools at the edge of each wash
139
+ float wet = length(img(q, 0.) - img(q, 3.5));
140
+ col = mix(col, col*col*1.12, uWash*.9*smoothstep(.02, .16, wet));
141
+ // blooms: the wash dried unevenly
142
+ col *= 1. + uWash*.3*(fbm(px/(95.*uS) + 3.) - .5);
143
+ col = mix(col, col*mix(vec3(1.), vec3(.92, .95, 1.05), fbm(px/(60.*uS))), uWash*.6);
144
+ // granulation in the tooth of the paper
145
+ col *= 1. - uWash*.24*(fbm(px/(2.2*uS)) - .5);
146
+ // the paper showing through where the brush ran dry
147
+ float dry = smoothstep(.62, .82, fbm(px/(22.*uS) + 7. + bt));
148
+ col = mix(col, uPaperCol, uWash*(.08 + .3*dry*smoothstep(.0, .12, wet)));
149
+ col = mix(col, uPaperCol, .12*uWash);
150
+ } else {
151
+ col = img(px, 0.);
152
+ }
153
+ if (uOutline > 0.){
154
+ float e = 1.4*uS;
155
+ float gx = lum(img(px + vec2(e,0.), .5)) - lum(img(px - vec2(e,0.), .5));
156
+ float gy = lum(img(px + vec2(0.,e), .5)) - lum(img(px - vec2(0.,e), .5));
157
+ float wob = .7 + .6*vnoise(px/(30.*uS) + uTick*.37);
158
+ float line = smoothstep(.06, .2, length(vec2(gx, gy))*wob);
159
+ col = mix(col, uInk, line*uOutline);
160
+ }
161
+ if (uPoster > 1.) col = floor(col*uPoster + .5) / uPoster;
162
+ // ground: canvas weave or paper tooth, then grain
163
+ float weave = sin(px.x*3.1416/(2.6*uS)) * sin(px.y*3.1416/(2.6*uS));
164
+ float tooth = fbm(px/(3.*uS));
165
+ col *= 1. + uPaper*(.05*weave + .09*(tooth - .5));
166
+ col += (hash12(px + fract(uTick*.137)*911.) - .5) * uGrain * .07;
167
+ // grade
168
+ col += uBloom * max(img(px, 5.) - .55, 0.) * 1.4;
169
+ float l = lum(col);
170
+ col = mix(vec3(l), col, uSat);
171
+ col = (col - .5)*uContrast + .5;
172
+ col += vec3(.035, .01, -.03)*uWarm;
173
+ vec2 uv = px / uRes - .5;
174
+ col *= 1. - uVig*dot(uv*vec2(1.2, .9), uv*vec2(1.2, .9))*1.3;
175
+ o = vec4(clamp(col, 0., 1.), 1.);
176
+ }`;
177
+
178
+ const DEFAULTS: PaintSettings = {
179
+ mode: "strokes", brush: 9, length: 2.6, layers: 3, swirl: 0.55, flow: 160, curve: 0.5,
180
+ bristle: 0.6, impasto: 0.5, hueJitter: 0.12, valueJitter: 0.14, satJitter: 0.25, respect: 0.85,
181
+ boil: 0.5, paper: 0.6, grain: 0.5, wash: 0, bleed: 0, outline: 0, ink: "#1a1612",
182
+ paperColor: "#f3eee2", posterize: 0, saturation: 1.12, contrast: 1.06, warmth: 0.2,
183
+ vignette: 0.35, bloom: 0.25,
184
+ };
185
+
186
+ const MODES = new Map<string, number>([["strokes", 0], ["wash", 1], ["flat", 2]]);
187
+
188
+ // shader uniform name -> the look setting that feeds it
189
+ const NUMBER_UNIFORMS: [string, PaintNumberKey][] = [
190
+ ["uBrush", "brush"], ["uLen", "length"], ["uLayers", "layers"], ["uSwirl", "swirl"],
191
+ ["uFlow", "flow"], ["uCurve", "curve"], ["uBristle", "bristle"], ["uImpasto", "impasto"],
192
+ ["uHueJ", "hueJitter"], ["uValJ", "valueJitter"], ["uSatJ", "satJitter"],
193
+ ["uRespect", "respect"], ["uBoil", "boil"], ["uPaper", "paper"], ["uGrain", "grain"],
194
+ ["uWash", "wash"], ["uBleed", "bleed"], ["uOutline", "outline"], ["uPoster", "posterize"],
195
+ ["uSat", "saturation"], ["uContrast", "contrast"], ["uWarm", "warmth"],
196
+ ["uVig", "vignette"], ["uBloom", "bloom"],
197
+ ];
198
+
199
+ function hex(c: string): number[] {
200
+ const n = parseInt(String(c).replace("#", ""), 16);
201
+ return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];
202
+ }
203
+
204
+ function compile(gl: WebGL2RenderingContext, type: number, src: string): WebGLShader {
205
+ const s = gl.createShader(type);
206
+ if (!s) throw new Error("WebGL2 could not create a shader");
207
+ gl.shaderSource(s, src);
208
+ gl.compileShader(s);
209
+ if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s) ?? "shader compile failed");
210
+ return s;
211
+ }
212
+
213
+ function link(gl: WebGL2RenderingContext): WebGLProgram {
214
+ const prog = gl.createProgram();
215
+ if (!prog) throw new Error("WebGL2 could not create a program");
216
+ gl.attachShader(prog, compile(gl, gl.VERTEX_SHADER, VS));
217
+ gl.attachShader(prog, compile(gl, gl.FRAGMENT_SHADER, FS));
218
+ gl.linkProgram(prog);
219
+ if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(prog) ?? "program link failed");
220
+ return prog;
221
+ }
222
+
223
+ function makeTexture(gl: WebGL2RenderingContext): WebGLTexture {
224
+ const tex = gl.createTexture();
225
+ if (!tex) throw new Error("WebGL2 could not create a texture");
226
+ gl.bindTexture(gl.TEXTURE_2D, tex);
227
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
228
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
229
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
230
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
231
+ return tex;
232
+ }
233
+
234
+ function Painter(canvas: HTMLCanvasElement): Painter {
235
+ const gl = canvas.getContext("webgl2", { preserveDrawingBuffer: true, antialias: false });
236
+ if (!gl) throw new Error("WebGL2 is not available");
237
+ const prog = link(gl);
238
+ gl.useProgram(prog);
239
+ const buf = gl.createBuffer();
240
+ gl.bindBuffer(gl.ARRAY_BUFFER, buf);
241
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
242
+ const loc = gl.getAttribLocation(prog, "p");
243
+ gl.enableVertexAttribArray(loc);
244
+ gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
245
+ const tex = makeTexture(gl);
246
+ const U = (n: string) => gl.getUniformLocation(prog, n);
247
+
248
+ return {
249
+ paint(flat, look, tick) {
250
+ const L = { ...DEFAULTS, ...look };
251
+ gl.viewport(0, 0, canvas.width, canvas.height);
252
+ gl.bindTexture(gl.TEXTURE_2D, tex);
253
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, flat);
254
+ gl.generateMipmap(gl.TEXTURE_2D);
255
+ gl.uniform2f(U("uRes"), canvas.width, canvas.height);
256
+ gl.uniform1f(U("uS"), canvas.height / 1920);
257
+ gl.uniform1f(U("uTick"), tick);
258
+ gl.uniform1f(U("uMode"), MODES.get(L.mode) ?? 0);
259
+ for (const [name, key] of NUMBER_UNIFORMS) gl.uniform1f(U(name), L[key]);
260
+ gl.uniform3fv(U("uInk"), hex(L.ink));
261
+ gl.uniform3fv(U("uPaperCol"), hex(L.paperColor));
262
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
263
+ },
264
+ };
265
+ }
266
+
267
+ window.ShortsPaint = { Painter, DEFAULTS };
268
+ })();
@@ -0,0 +1,181 @@
1
+ /*
2
+ * THE STAGE: which shot is on screen at a frame, the camera, the paint pass
3
+ * and the caption. window.SHORT (the timeline the CLI builds) and
4
+ * window.SCENES (the project's scenes.js) are loaded before this.
5
+ *
6
+ * window.drawing(d) → paints drawing d (held `twos` frames) and returns it as a JPEG data URL
7
+ * window.still(i, at) → shot i at progress `at` (0..1), for contact sheets
8
+ */
9
+ (function () {
10
+ const S = window.SHORT;
11
+ const { W, H, fps, twos } = S;
12
+
13
+ function context2d(canvas: HTMLCanvasElement): CanvasRenderingContext2D {
14
+ const g = canvas.getContext("2d");
15
+ if (!g) throw new Error("2D canvas is not available");
16
+ return g;
17
+ }
18
+
19
+ function layer(): { canvas: HTMLCanvasElement; g: CanvasRenderingContext2D } {
20
+ const canvas = document.createElement("canvas");
21
+ canvas.width = W; canvas.height = H;
22
+ return { canvas, g: context2d(canvas) };
23
+ }
24
+
25
+ const { canvas: flat, g: fg } = layer();
26
+ const { canvas: under, g: ug } = layer();
27
+ const glc = document.createElement("canvas");
28
+ glc.width = W; glc.height = H;
29
+ const painter = window.ShortsPaint.Painter(glc);
30
+ const out = document.getElementById("out");
31
+ if (!(out instanceof HTMLCanvasElement)) throw new Error('no <canvas id="out"> on the page');
32
+ out.width = W; out.height = H;
33
+ const og = context2d(out);
34
+
35
+ const ease = (x: number) => x * x * (3 - 2 * x);
36
+
37
+ /** Scale and offset of the camera at progress eased e, strength a. */
38
+ type CameraPose = (a: number, e: number, p: number) => [number, number, number];
39
+ const CAMERA_MOVES = new Map<string, CameraPose>([
40
+ ["push", (a, e) => [1 + 0.08 * a * e, 0, 0]],
41
+ ["pull", (a, e) => [1 + 0.08 * a * (1 - e), 0, 0]],
42
+ ["pan-left", (a, e) => [1.12, W * 0.05 * a * (1 - 2 * e), 0]],
43
+ ["pan-right", (a, e) => [1.12, -W * 0.05 * a * (1 - 2 * e), 0]],
44
+ ["rise", (a, e) => [1.1, 0, H * 0.04 * a * (1 - 2 * e)]],
45
+ ["fall", (a, e) => [1.1, 0, -H * 0.04 * a * (1 - 2 * e)]],
46
+ ["drift", (a, _e, p) => [1.05, W * 0.015 * a * Math.sin(p * 2.2), H * 0.008 * a * Math.cos(p * 1.7)]],
47
+ ]);
48
+
49
+ /** The camera, as a transform about the frame centre. Moves are slow on purpose. */
50
+ function camera(g: CanvasRenderingContext2D, move: string | undefined, p: number, amount: number | undefined): void {
51
+ const a = amount ?? 1;
52
+ const e = ease(Math.min(1, Math.max(0, p)));
53
+ const [s, x, y] = (move === undefined ? undefined : CAMERA_MOVES.get(move))?.(a, e, p) ?? [1, 0, 0];
54
+ g.setTransform(s, 0, 0, s, W / 2 - (W / 2) * s + x, H / 2 - (H / 2) * s + y);
55
+ }
56
+
57
+ function drawMissingScene(g: CanvasRenderingContext2D, name: string): void {
58
+ g.fillStyle = "#c33"; g.fillRect(0, 0, W, H);
59
+ g.fillStyle = "#fff"; g.font = `${60 * H / 1920}px sans-serif`;
60
+ g.fillText(`no scene "${name}"`, 40, H / 2);
61
+ console.error(`no scene "${name}" in scenes.js`);
62
+ }
63
+
64
+ function drawBeat(g: CanvasRenderingContext2D, i: number, t: number): void {
65
+ const b = S.beats[i];
66
+ const fn = window.SCENES[b.scene];
67
+ g.save();
68
+ g.setTransform(1, 0, 0, 1, 0, 0);
69
+ g.fillStyle = "#000";
70
+ g.fillRect(0, 0, W, H);
71
+ camera(g, b.move, t / b.dur, b.moveAmount);
72
+ if (!fn) drawMissingScene(g, b.scene);
73
+ else {
74
+ const k = window.ShortsKit.make(g, W, H, t, Math.min(1, t / b.dur), window.ShortsKit.hashStr(b.scene + (b.seed ?? "")), S.look.palette, b);
75
+ try { fn(k); } catch (e) { console.error(`scene "${b.scene}": ${e instanceof Error ? e.message : String(e)}`); }
76
+ }
77
+ g.restore();
78
+ }
79
+
80
+ function beatAt(time: number): number {
81
+ let i = 0;
82
+ while (i < S.beats.length - 1 && time >= S.beats[i + 1].start) i++;
83
+ return i;
84
+ }
85
+
86
+ function captionText(text: string, C: Captions): string {
87
+ return C.case === "upper" ? text.toUpperCase() : C.case === "lower" ? text.toLowerCase() : text;
88
+ }
89
+
90
+ function caption(text: string): void {
91
+ const C = S.captions || {};
92
+ if (!text || C.style === "none") return;
93
+ const size = (C.size ?? 44) * (H / 1920) * (W > H ? 1.25 : 1);
94
+ og.font = `${C.weight ?? 500} ${size}px ${C.font ?? '"Helvetica Neue", Helvetica, Arial, sans-serif'}`;
95
+ og.textAlign = "center";
96
+ og.textBaseline = "middle";
97
+ const lines = wrap(captionText(text, C), W * (C.width ?? 0.78), og);
98
+ const lh = size * 1.3;
99
+ const y0 = H * (C.y ?? 0.775) - ((lines.length - 1) * lh) / 2;
100
+ lines.forEach((ln, j) => {
101
+ const y = y0 + j * lh;
102
+ if ((C.style ?? "box") === "box") {
103
+ const w = og.measureText(ln).width + size * 0.7;
104
+ og.fillStyle = C.box ?? "rgba(20,20,20,0.62)";
105
+ og.fillRect(W / 2 - w / 2, y - lh / 2 + size * 0.04, w, lh - size * 0.08);
106
+ } else {
107
+ og.shadowColor = "rgba(0,0,0,0.75)";
108
+ og.shadowBlur = size * 0.35;
109
+ }
110
+ og.fillStyle = C.color ?? "#ffffff";
111
+ og.fillText(ln, W / 2, y + size * 0.04);
112
+ og.shadowBlur = 0;
113
+ });
114
+ }
115
+
116
+ function wrap(text: string, max: number, g: CanvasRenderingContext2D): string[] {
117
+ const words = text.split(/\s+/), lines: string[] = [];
118
+ let cur = "";
119
+ for (const w of words) {
120
+ const next = cur ? cur + " " + w : w;
121
+ if (g.measureText(next).width > max && cur) { lines.push(cur); cur = w; } else cur = next;
122
+ }
123
+ if (cur) lines.push(cur);
124
+ return lines;
125
+ }
126
+
127
+ /** The words on screen at a time: the caption track (chunks of the line, timed to the voice). */
128
+ function captionFor(time: number, b: Beat): string {
129
+ if (S.track) {
130
+ for (const c of S.track) if (time >= c.at && time < c.end) return c.text;
131
+ return "";
132
+ }
133
+ return b.caption ?? b.say ?? "";
134
+ }
135
+
136
+ /** How long the soft cut into this shot lasts: "fade" or "dip", else none. */
137
+ function fadeFor(b: Beat): number {
138
+ return b.transition === "fade" ? (b.fadeDur ?? 0.5) : b.transition === "dip" ? (b.fadeDur ?? 0.7) : 0;
139
+ }
140
+
141
+ // a soft cut: the end of the previous shot fades under this one ("fade"), or through black ("dip")
142
+ function softCut(i: number, b: Beat, t: number, fade: number): void {
143
+ const prev = S.beats[i - 1];
144
+ fg.save();
145
+ if (b.transition === "dip") {
146
+ fg.globalAlpha = 1 - t / fade;
147
+ fg.fillStyle = "#000";
148
+ fg.fillRect(0, 0, W, H);
149
+ } else {
150
+ drawBeat(ug, i - 1, prev.dur);
151
+ fg.globalAlpha = 1 - t / fade;
152
+ fg.drawImage(under, 0, 0);
153
+ }
154
+ fg.restore();
155
+ }
156
+
157
+ function render(time: number, tick: number): void {
158
+ const i = beatAt(time);
159
+ const b = S.beats[i];
160
+ const t = time - b.start;
161
+ drawBeat(fg, i, t);
162
+ const fade = fadeFor(b);
163
+ if (fade && i > 0 && t < fade) softCut(i, b, t, fade);
164
+ painter.paint(flat, S.look, tick);
165
+ og.drawImage(glc, 0, 0);
166
+ caption(captionFor(time, b));
167
+ }
168
+
169
+ window.drawing = (d) => {
170
+ const time = (d * twos) / fps + 0.0001;
171
+ render(time, d);
172
+ return out.toDataURL("image/jpeg", 0.93);
173
+ };
174
+ window.still = (i, at) => {
175
+ const b = S.beats[i];
176
+ const time = b.start + b.dur * at;
177
+ render(time, Math.round((time * fps) / twos));
178
+ return out.toDataURL("image/jpeg", 0.9);
179
+ };
180
+ window.ready = true;
181
+ })();
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2023",
4
+ "lib": ["es2023", "dom"],
5
+ "types": [],
6
+ "strict": true,
7
+ "noEmit": true,
8
+ "erasableSyntaxOnly": true,
9
+ "skipLibCheck": true
10
+ },
11
+ "include": ["*.ts"]
12
+ }