frameyard 0.7.0 → 0.9.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 (334) hide show
  1. package/CHANGELOG.md +164 -7
  2. package/GLOSSARY.md +152 -18
  3. package/README.md +328 -285
  4. package/dist/assets/integrations.d.ts +8 -4
  5. package/dist/assets/integrations.d.ts.map +1 -1
  6. package/dist/assets/integrations.js +8 -4
  7. package/dist/assets/integrations.js.map +1 -1
  8. package/dist/assets/provenance.d.ts +2 -2
  9. package/dist/assets/provenance.d.ts.map +1 -1
  10. package/dist/assets/provenance.js +5 -3
  11. package/dist/assets/provenance.js.map +1 -1
  12. package/dist/assets/tts-supertonic.d.ts +28 -0
  13. package/dist/assets/tts-supertonic.d.ts.map +1 -0
  14. package/dist/assets/tts-supertonic.js +156 -0
  15. package/dist/assets/tts-supertonic.js.map +1 -0
  16. package/dist/assets/tts.d.ts +18 -6
  17. package/dist/assets/tts.d.ts.map +1 -1
  18. package/dist/assets/tts.js +219 -97
  19. package/dist/assets/tts.js.map +1 -1
  20. package/dist/cli.js +1705 -106
  21. package/dist/cli.js.map +1 -1
  22. package/dist/composition/descriptor.d.ts +17 -3
  23. package/dist/composition/descriptor.d.ts.map +1 -1
  24. package/dist/composition/descriptor.js +51 -28
  25. package/dist/composition/descriptor.js.map +1 -1
  26. package/dist/composition/format.d.ts +12 -0
  27. package/dist/composition/format.d.ts.map +1 -0
  28. package/dist/composition/format.js +28 -0
  29. package/dist/composition/format.js.map +1 -0
  30. package/dist/composition/index.d.ts +2 -1
  31. package/dist/composition/index.d.ts.map +1 -1
  32. package/dist/composition/index.js +1 -0
  33. package/dist/composition/index.js.map +1 -1
  34. package/dist/context.d.ts +82 -0
  35. package/dist/context.d.ts.map +1 -0
  36. package/dist/context.js +277 -0
  37. package/dist/context.js.map +1 -0
  38. package/dist/contracts.d.ts +80 -6
  39. package/dist/contracts.d.ts.map +1 -1
  40. package/dist/contracts.js +3 -0
  41. package/dist/contracts.js.map +1 -1
  42. package/dist/doctor.d.ts +14 -1
  43. package/dist/doctor.d.ts.map +1 -1
  44. package/dist/doctor.js +166 -7
  45. package/dist/doctor.js.map +1 -1
  46. package/dist/errors.d.ts +1 -1
  47. package/dist/errors.d.ts.map +1 -1
  48. package/dist/errors.js.map +1 -1
  49. package/dist/fonts/add.d.ts +41 -0
  50. package/dist/fonts/add.d.ts.map +1 -0
  51. package/dist/fonts/add.js +259 -0
  52. package/dist/fonts/add.js.map +1 -0
  53. package/dist/fonts/binary.d.ts +4 -0
  54. package/dist/fonts/binary.d.ts.map +1 -0
  55. package/dist/fonts/binary.js +21 -0
  56. package/dist/fonts/binary.js.map +1 -0
  57. package/dist/fonts/css.d.ts +4 -0
  58. package/dist/fonts/css.d.ts.map +1 -0
  59. package/dist/fonts/css.js +50 -0
  60. package/dist/fonts/css.js.map +1 -0
  61. package/dist/fonts/fontsource.d.ts +107 -0
  62. package/dist/fonts/fontsource.d.ts.map +1 -0
  63. package/dist/fonts/fontsource.js +565 -0
  64. package/dist/fonts/fontsource.js.map +1 -0
  65. package/dist/fonts/index.d.ts +11 -0
  66. package/dist/fonts/index.d.ts.map +1 -0
  67. package/dist/fonts/index.js +11 -0
  68. package/dist/fonts/index.js.map +1 -0
  69. package/dist/fonts/install.d.ts +24 -0
  70. package/dist/fonts/install.d.ts.map +1 -0
  71. package/dist/fonts/install.js +145 -0
  72. package/dist/fonts/install.js.map +1 -0
  73. package/dist/fonts/manifest.d.ts +21 -0
  74. package/dist/fonts/manifest.d.ts.map +1 -0
  75. package/dist/fonts/manifest.js +96 -0
  76. package/dist/fonts/manifest.js.map +1 -0
  77. package/dist/fonts/options.d.ts +9 -0
  78. package/dist/fonts/options.d.ts.map +1 -0
  79. package/dist/fonts/options.js +66 -0
  80. package/dist/fonts/options.js.map +1 -0
  81. package/dist/fonts/schema.d.ts +4 -0
  82. package/dist/fonts/schema.d.ts.map +1 -0
  83. package/dist/fonts/schema.js +316 -0
  84. package/dist/fonts/schema.js.map +1 -0
  85. package/dist/fonts/summary.d.ts +16 -0
  86. package/dist/fonts/summary.d.ts.map +1 -0
  87. package/dist/fonts/summary.js +22 -0
  88. package/dist/fonts/summary.js.map +1 -0
  89. package/dist/fonts/types.d.ts +56 -0
  90. package/dist/fonts/types.d.ts.map +1 -0
  91. package/dist/fonts/types.js +8 -0
  92. package/dist/fonts/types.js.map +1 -0
  93. package/dist/fonts/verify.d.ts +33 -0
  94. package/dist/fonts/verify.d.ts.map +1 -0
  95. package/dist/fonts/verify.js +200 -0
  96. package/dist/fonts/verify.js.map +1 -0
  97. package/dist/html/document-adapter.d.ts +3 -3
  98. package/dist/html/document-adapter.d.ts.map +1 -1
  99. package/dist/html/document-adapter.js +1 -1
  100. package/dist/html/document-adapter.js.map +1 -1
  101. package/dist/html/index.d.ts +1 -1
  102. package/dist/html/index.d.ts.map +1 -1
  103. package/dist/html/parse.d.ts +10 -2
  104. package/dist/html/parse.d.ts.map +1 -1
  105. package/dist/html/parse.js +14 -5
  106. package/dist/html/parse.js.map +1 -1
  107. package/dist/html/validate.d.ts +12 -1
  108. package/dist/html/validate.d.ts.map +1 -1
  109. package/dist/html/validate.js +292 -61
  110. package/dist/html/validate.js.map +1 -1
  111. package/dist/index.d.ts +4 -4
  112. package/dist/index.d.ts.map +1 -1
  113. package/dist/index.js +2 -2
  114. package/dist/index.js.map +1 -1
  115. package/dist/inspect.d.ts +61 -0
  116. package/dist/inspect.d.ts.map +1 -0
  117. package/dist/inspect.js +325 -0
  118. package/dist/inspect.js.map +1 -0
  119. package/dist/lint.d.ts +63 -0
  120. package/dist/lint.d.ts.map +1 -0
  121. package/dist/lint.js +265 -0
  122. package/dist/lint.js.map +1 -0
  123. package/dist/policy.d.ts +69 -0
  124. package/dist/policy.d.ts.map +1 -0
  125. package/dist/policy.js +361 -0
  126. package/dist/policy.js.map +1 -0
  127. package/dist/preview/main.d.ts +2 -2
  128. package/dist/preview/main.d.ts.map +1 -1
  129. package/dist/preview/main.js +35 -14
  130. package/dist/preview/main.js.map +1 -1
  131. package/dist/preview/timeline-data.d.ts +17 -65
  132. package/dist/preview/timeline-data.d.ts.map +1 -1
  133. package/dist/preview/timeline-host.d.ts.map +1 -1
  134. package/dist/preview/timeline-host.js +73 -38
  135. package/dist/preview/timeline-host.js.map +1 -1
  136. package/dist/preview/timeline-main.d.ts.map +1 -1
  137. package/dist/preview/timeline-main.js +47 -3
  138. package/dist/preview/timeline-main.js.map +1 -1
  139. package/dist/project.d.ts +13 -8
  140. package/dist/project.d.ts.map +1 -1
  141. package/dist/project.js +20 -17
  142. package/dist/project.js.map +1 -1
  143. package/dist/renderer/browser.d.ts +2 -2
  144. package/dist/renderer/browser.d.ts.map +1 -1
  145. package/dist/renderer/browser.js +10 -4
  146. package/dist/renderer/browser.js.map +1 -1
  147. package/dist/renderer/ffmpeg.d.ts +44 -1
  148. package/dist/renderer/ffmpeg.d.ts.map +1 -1
  149. package/dist/renderer/ffmpeg.js +371 -73
  150. package/dist/renderer/ffmpeg.js.map +1 -1
  151. package/dist/renderer/index.d.ts +40 -4
  152. package/dist/renderer/index.d.ts.map +1 -1
  153. package/dist/renderer/index.js +100 -25
  154. package/dist/renderer/index.js.map +1 -1
  155. package/dist/renderer/keyframe-expressions.d.ts +39 -0
  156. package/dist/renderer/keyframe-expressions.d.ts.map +1 -0
  157. package/dist/renderer/keyframe-expressions.js +180 -0
  158. package/dist/renderer/keyframe-expressions.js.map +1 -0
  159. package/dist/renderer/timeline.d.ts +2 -0
  160. package/dist/renderer/timeline.d.ts.map +1 -1
  161. package/dist/renderer/timeline.js +168 -106
  162. package/dist/renderer/timeline.js.map +1 -1
  163. package/dist/runtime/host.d.ts.map +1 -1
  164. package/dist/runtime/host.js +159 -31
  165. package/dist/runtime/host.js.map +1 -1
  166. package/dist/scaffold.d.ts +3 -0
  167. package/dist/scaffold.d.ts.map +1 -1
  168. package/dist/scaffold.js +48 -317
  169. package/dist/scaffold.js.map +1 -1
  170. package/dist/server/composition-html.d.ts +15 -7
  171. package/dist/server/composition-html.d.ts.map +1 -1
  172. package/dist/server/composition-html.js +72 -16
  173. package/dist/server/composition-html.js.map +1 -1
  174. package/dist/server/server.d.ts.map +1 -1
  175. package/dist/server/server.js +56 -6
  176. package/dist/server/server.js.map +1 -1
  177. package/dist/server/timeline-preview-data.d.ts.map +1 -1
  178. package/dist/server/timeline-preview-data.js +12 -23
  179. package/dist/server/timeline-preview-data.js.map +1 -1
  180. package/dist/server/timeline-preview-html.d.ts.map +1 -1
  181. package/dist/server/timeline-preview-html.js +20 -3
  182. package/dist/server/timeline-preview-html.js.map +1 -1
  183. package/dist/templates.d.ts +16 -0
  184. package/dist/templates.d.ts.map +1 -0
  185. package/dist/templates.js +186 -0
  186. package/dist/templates.js.map +1 -0
  187. package/dist/timeline/easing.d.ts +36 -0
  188. package/dist/timeline/easing.d.ts.map +1 -0
  189. package/dist/timeline/easing.js +225 -0
  190. package/dist/timeline/easing.js.map +1 -0
  191. package/dist/timeline/keyframes.d.ts +23 -0
  192. package/dist/timeline/keyframes.d.ts.map +1 -0
  193. package/dist/timeline/keyframes.js +95 -0
  194. package/dist/timeline/keyframes.js.map +1 -0
  195. package/dist/timeline/plan.d.ts +6 -12
  196. package/dist/timeline/plan.d.ts.map +1 -1
  197. package/dist/timeline/plan.js +17 -30
  198. package/dist/timeline/plan.js.map +1 -1
  199. package/dist/timeline/resolve.d.ts +16 -3
  200. package/dist/timeline/resolve.d.ts.map +1 -1
  201. package/dist/timeline/resolve.js +119 -13
  202. package/dist/timeline/resolve.js.map +1 -1
  203. package/dist/timeline/scene.d.ts +126 -0
  204. package/dist/timeline/scene.d.ts.map +1 -0
  205. package/dist/timeline/scene.js +109 -0
  206. package/dist/timeline/scene.js.map +1 -0
  207. package/dist/timeline/schema.d.ts +38 -0
  208. package/dist/timeline/schema.d.ts.map +1 -1
  209. package/dist/timeline/schema.js +290 -9
  210. package/dist/timeline/schema.js.map +1 -1
  211. package/dist/timeline/transitions.d.ts +21 -0
  212. package/dist/timeline/transitions.d.ts.map +1 -0
  213. package/dist/timeline/transitions.js +23 -0
  214. package/dist/timeline/transitions.js.map +1 -0
  215. package/dist/version.d.ts +1 -1
  216. package/dist/version.js +1 -1
  217. package/docs/adr/0007-make-html-documents-the-composition-unit.md +2 -2
  218. package/docs/adr/0008-share-the-composition-document-grammar.md +1 -1
  219. package/docs/adr/0010-make-timelines-the-render-unit.md +3 -2
  220. package/docs/adr/0011-compose-timeline-frames-in-a-live-browser-host.md +1 -1
  221. package/docs/adr/0012-render-timelines-with-segmented-lossless-clip-intermediates.md +1 -1
  222. package/docs/adr/0014-compose-timeline-clips-with-css-blend-modes.md +38 -0
  223. package/docs/adr/0015-inherit-composition-format-from-the-project.md +28 -0
  224. package/docs/adr/0016-author-compositions-as-html-fragments.md +32 -0
  225. package/docs/adr/0017-render-timeless-compositions-as-stills.md +99 -0
  226. package/docs/adr/0018-animate-timeline-clips-with-keyframes.md +35 -0
  227. package/docs/adr/0019-make-same-track-overlap-the-transition-window.md +36 -0
  228. package/docs/adr/0020-crossfade-audio-clips-on-audio-tracks.md +27 -0
  229. package/docs/adr/0021-project-timeline-scenes-once.md +32 -0
  230. package/docs/adr/0022-gate-heavy-operations-with-a-workspace-resource-policy.md +35 -0
  231. package/docs/adr/0023-pin-fonts-as-project-assets.md +98 -0
  232. package/docs/migration-0.4.md +5 -3
  233. package/docs/migration-0.8.md +163 -0
  234. package/docs/preview-testing.md +23 -3
  235. package/docs/{v0.1-design.md → v0.x-design.md} +86 -53
  236. package/examples/blend/README.md +106 -0
  237. package/examples/blend/assets/ink.mp4 +0 -0
  238. package/examples/blend/compositions/aurora.html +55 -0
  239. package/examples/blend/compositions/backdrop.html +74 -0
  240. package/examples/blend/compositions/labels.html +149 -0
  241. package/examples/blend/fy.config.js +11 -0
  242. package/examples/blend/scripts/make-ink.sh +26 -0
  243. package/examples/blend/timelines/blend.timeline.json +155 -0
  244. package/examples/demo/README.md +2 -2
  245. package/examples/demo/compositions/demo.html +243 -251
  246. package/examples/intro/README.md +71 -0
  247. package/examples/intro/assets/fonts/figtree/LICENSE.txt +93 -0
  248. package/examples/intro/assets/fonts/figtree/figtree-latin-wght-normal.woff2 +0 -0
  249. package/examples/intro/assets/fonts/instrument-serif/LICENSE.txt +93 -0
  250. package/examples/intro/assets/fonts/instrument-serif/instrument-serif-latin-400-italic.woff2 +0 -0
  251. package/examples/intro/assets/fonts/space-mono/LICENSE.txt +93 -0
  252. package/examples/intro/assets/fonts/space-mono/space-mono-latin-400-normal.woff2 +0 -0
  253. package/examples/intro/assets/fonts/space-mono/space-mono-latin-700-normal.woff2 +0 -0
  254. package/examples/intro/assets/grain.png +0 -0
  255. package/examples/intro/assets/score.ogg +0 -0
  256. package/examples/intro/compositions/brand.html +412 -0
  257. package/examples/intro/compositions/chrome.controller.js +63 -0
  258. package/examples/intro/compositions/chrome.html +256 -0
  259. package/examples/intro/compositions/lattice.controller.js +148 -0
  260. package/examples/intro/compositions/lattice.html +308 -0
  261. package/examples/intro/compositions/opening.html +504 -0
  262. package/examples/intro/compositions/studio.html +930 -0
  263. package/examples/intro/compositions/theme.css +218 -0
  264. package/examples/intro/fy.config.js +13 -0
  265. package/examples/intro/fy.fonts.json +100 -0
  266. package/examples/intro/scripts/make-grain.mjs +79 -0
  267. package/examples/intro/scripts/make-score.mjs +702 -0
  268. package/examples/intro/timelines/intro.timeline.json +60 -0
  269. package/examples/keyframes/README.md +76 -0
  270. package/examples/keyframes/compositions/backdrop.html +60 -0
  271. package/examples/keyframes/compositions/card.html +99 -0
  272. package/examples/keyframes/fy.config.js +10 -0
  273. package/examples/keyframes/timelines/keyframes.timeline.json +60 -0
  274. package/examples/nested/README.md +112 -0
  275. package/examples/nested/assets/pulse.wav +0 -0
  276. package/examples/nested/compositions/backdrop.html +84 -0
  277. package/examples/nested/compositions/card.html +275 -0
  278. package/examples/nested/compositions/overlay.html +307 -0
  279. package/examples/nested/fy.config.js +13 -0
  280. package/examples/nested/scripts/make-pulse.sh +20 -0
  281. package/examples/nested/timelines/chapter-a.timeline.json +39 -0
  282. package/examples/nested/timelines/chapter-b.timeline.json +39 -0
  283. package/examples/nested/timelines/nested-reel.timeline.json +52 -0
  284. package/examples/static/README.md +17 -0
  285. package/examples/static/compositions/still.html +37 -0
  286. package/examples/static/fy.config.js +9 -0
  287. package/examples/static/timelines/still.timeline.json +13 -0
  288. package/examples/transitions/README.md +128 -0
  289. package/examples/transitions/assets/alpha.wav +0 -0
  290. package/examples/transitions/assets/beta.wav +0 -0
  291. package/examples/transitions/assets/gamma.wav +0 -0
  292. package/examples/transitions/compositions/alpha.html +56 -0
  293. package/examples/transitions/compositions/beta.html +56 -0
  294. package/examples/transitions/compositions/gamma.html +56 -0
  295. package/examples/transitions/fy.config.js +12 -0
  296. package/examples/transitions/timelines/audio-crossfade.timeline.json +44 -0
  297. package/examples/transitions/timelines/transitions.timeline.json +31 -0
  298. package/examples/video/README.md +4 -4
  299. package/examples/video/compositions/video-mp4.html +349 -357
  300. package/examples/video/compositions/video-overlay.html +337 -345
  301. package/examples/video/compositions/video.html +349 -357
  302. package/examples/webgl/README.md +1 -1
  303. package/examples/webgl/compositions/webgl.html +205 -213
  304. package/package.json +13 -5
  305. package/scaffolds/init/AGENTS.md +131 -0
  306. package/scaffolds/init/README.md +56 -0
  307. package/scaffolds/init/compositions/hello.html +79 -0
  308. package/scaffolds/init/fy.config.js +9 -0
  309. package/scaffolds/init/gitignore.template +1 -0
  310. package/scaffolds/init/package.json +11 -0
  311. package/scaffolds/init/props/hello.json +4 -0
  312. package/scaffolds/init/props/main.json +6 -0
  313. package/scaffolds/init/timelines/main.timeline.json +20 -0
  314. package/scaffolds/local-models/LOCAL-MODELS.md +77 -0
  315. package/scaffolds/local-models/fy.policy.json +5 -0
  316. package/scaffolds/local-models/opencode/opencode.jsonc +17 -0
  317. package/scaffolds/local-models/pi/APPEND_SYSTEM.md +21 -0
  318. package/scaffolds/local-models/pi/sandbox.json +15 -0
  319. package/scaffolds/local-models/pi/settings.json +15 -0
  320. package/skills/frameyard/README.md +1 -0
  321. package/skills/frameyard/SKILL.md +59 -59
  322. package/skills/frameyard/references/audio-assets.md +32 -3
  323. package/skills/frameyard/references/common-mistakes.md +81 -7
  324. package/skills/frameyard/references/controllers.md +5 -4
  325. package/skills/frameyard/references/deterministic-authoring.md +49 -37
  326. package/skills/frameyard/references/diagnostics.md +42 -4
  327. package/skills/frameyard/references/failure-recovery.md +56 -10
  328. package/skills/frameyard/references/fonts.md +132 -0
  329. package/skills/frameyard/references/json-results.md +165 -10
  330. package/skills/frameyard/references/quick-reference.md +222 -0
  331. package/skills/frameyard/references/timelines.md +109 -15
  332. /package/examples/demo/{video.config.js → fy.config.js} +0 -0
  333. /package/examples/video/{video.config.js → fy.config.js} +0 -0
  334. /package/examples/webgl/{video.config.js → fy.config.js} +0 -0
@@ -0,0 +1,52 @@
1
+ {
2
+ "version": 1,
3
+ "name": "Nested timelines: A full, B full, A as a group",
4
+ "durationInFrames": 360,
5
+ "background": "#05070d",
6
+ "tracks": [
7
+ { "id": "v1", "kind": "video", "name": "Chapter A · full frame" },
8
+ { "id": "v2", "kind": "video", "name": "Chapter B · full frame" },
9
+ { "id": "v3", "kind": "video", "name": "Chapter A · group transform" },
10
+ { "id": "v4", "kind": "video", "name": "Overlay" }
11
+ ],
12
+ "clips": [
13
+ {
14
+ "id": "chapter-a-full",
15
+ "track": "v1",
16
+ "source": { "kind": "timeline", "id": "chapter-a" },
17
+ "start": 0,
18
+ "duration": 120
19
+ },
20
+ {
21
+ "id": "chapter-b-full",
22
+ "track": "v2",
23
+ "source": { "kind": "timeline", "id": "chapter-b" },
24
+ "start": 120,
25
+ "duration": 120
26
+ },
27
+ {
28
+ "id": "chapter-a-group",
29
+ "track": "v3",
30
+ "source": { "kind": "timeline", "id": "chapter-a" },
31
+ "start": 240,
32
+ "duration": 120,
33
+ "transform": {
34
+ "fit": "cover",
35
+ "width": 480,
36
+ "height": 270,
37
+ "x": 360,
38
+ "y": 180,
39
+ "rotation": 0,
40
+ "opacity": 1
41
+ },
42
+ "audio": { "muted": true }
43
+ },
44
+ {
45
+ "id": "overlay",
46
+ "track": "v4",
47
+ "source": { "kind": "composition", "id": "overlay" },
48
+ "start": 0,
49
+ "duration": 360
50
+ }
51
+ ]
52
+ }
@@ -0,0 +1,17 @@
1
+ # Static composition example
2
+
3
+ `compositions/still.html` declares neither `duration-in-frames` nor `fps`, so
4
+ Frameyard treats it as a static composition. The project `format` supplies the
5
+ 1280×720 dimensions. Rendering the composition writes a single PNG; the
6
+ timeline uses the same composition as an image layer for 90 frames.
7
+
8
+ ```sh
9
+ node dist/cli.js check still --project examples/static
10
+ FRAMEYARD_POLICY=full node dist/cli.js render still --project examples/static --out examples/static/out/still.png
11
+ node dist/cli.js check still-timeline --project examples/static
12
+ FRAMEYARD_POLICY=full node dist/cli.js render still-timeline --project examples/static --out examples/static/out/still-timeline.mp4
13
+ ```
14
+
15
+ `render still` defaults to `out/still.png`. Static compositions render to PNG;
16
+ `--container`, `--codec`, `--encoder`, and `--concurrency` are not valid for
17
+ them. Use the timeline when you need a video, audio, or a duration.
@@ -0,0 +1,37 @@
1
+ <style>
2
+ html,
3
+ body {
4
+ margin: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ background: radial-gradient(circle at 50% 40%, #1b3b4a 0%, #08161d 70%);
8
+ }
9
+ .card {
10
+ position: absolute;
11
+ inset: 0;
12
+ display: grid;
13
+ place-items: center;
14
+ color: #b8f36b;
15
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
16
+ text-align: center;
17
+ }
18
+ .card h1 {
19
+ margin: 0;
20
+ font-size: 72px;
21
+ letter-spacing: 0.08em;
22
+ text-transform: uppercase;
23
+ }
24
+ .card p {
25
+ margin: 16px 0 0;
26
+ color: #e6f4ea;
27
+ font-size: 22px;
28
+ }
29
+ </style>
30
+ <fy-composition>
31
+ <div class="card">
32
+ <div>
33
+ <h1>Static</h1>
34
+ <p>No duration, no frame rate, one capture.</p>
35
+ </div>
36
+ </div>
37
+ </fy-composition>
@@ -0,0 +1,9 @@
1
+ export default {
2
+ format: { width: 1280, height: 720, fps: 30 },
3
+ compositions: {
4
+ still: "./compositions/still.html",
5
+ },
6
+ timelines: {
7
+ "still-timeline": "./timelines/still.timeline.json",
8
+ },
9
+ };
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": 1,
3
+ "tracks": [{ "id": "v1", "kind": "video" }],
4
+ "clips": [
5
+ {
6
+ "id": "still",
7
+ "track": "v1",
8
+ "source": { "kind": "composition", "id": "still" },
9
+ "start": 0,
10
+ "duration": 90
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,128 @@
1
+ # Timeline transitions example
2
+
3
+ This project has two timelines:
4
+
5
+ - `transitions` dissolves three timed composition cards on one video track and
6
+ lets their contributed audio crossfade.
7
+ - `audio-crossfade` mutes the composition audio and places the sound directly on
8
+ an audio track, with a `crossfade` between two audio clips.
9
+
10
+ ## Visual dissolve with contributed audio
11
+
12
+ `transitions` is a 4-second, 1280×720, 30 fps timeline:
13
+
14
+ - **alpha** `0..60` — the A-major card and the outgoing clip. It keeps rendering
15
+ for the whole overlap, including after `beta` starts.
16
+ - **beta** `45..105` — the C#-major card and the incoming clip. Its first 15
17
+ frames are the `alpha → beta` dissolve, then it runs alone, then it hands off
18
+ to `gamma`.
19
+ - **gamma** `90..120` — the E-major card and the second incoming clip.
20
+
21
+ Each composition is a timed HTML fragment (`duration-in-frames`) with one
22
+ `<audio>` element. The audio belongs to the visual clip, so the dissolve
23
+ crossfades it automatically.
24
+
25
+ ## Audio clips on the timeline
26
+
27
+ `audio-crossfade` renders the same alpha and beta cards for 3.5 seconds, but
28
+ mutes their composition audio with `audio.muted: true` and puts the sound on
29
+ `a1` instead:
30
+
31
+ - **chord-alpha** `0..60` — `assets/alpha.wav`, the outgoing chord.
32
+ - **chord-beta** `45..105` — `assets/beta.wav`, the incoming chord.
33
+
34
+ ```json
35
+ "transitions": [
36
+ { "from": "alpha", "to": "beta", "kind": "dissolve", "easing": "ease-in-out" },
37
+ { "from": "chord-alpha", "to": "chord-beta", "kind": "crossfade", "easing": "linear" }
38
+ ]
39
+ ```
40
+
41
+ The visual dissolve and the audio crossfade share the same 15-frame overlap, so
42
+ the picture and the sound hand off together. `crossfade` is the audio-track
43
+ kind: it applies an equal-power gain ramp and changes no pixels.
44
+
45
+ ## Transition contract
46
+
47
+ `transitions` is a timeline-level array of `{ "from", "to", "kind", "easing" }`:
48
+
49
+ - `from` and `to` are clip IDs on the **same track**. A transition never spans
50
+ tracks.
51
+ - `kind` is `dissolve` on a video track or `crossfade` on an audio track. A
52
+ dissolve fades the incoming picture in and also crossfades the audio the two
53
+ clips contribute; a crossfade only ramps audio gain. Using the wrong kind for
54
+ the track is an error.
55
+ - `easing` defaults to `linear` and accepts the same CSS timing-function
56
+ vocabulary as clip keyframes.
57
+ - The transition window is the overlap: `to.start .. from.end`. There is no
58
+ `duration` field; move the incoming clip earlier to make the transition
59
+ longer.
60
+ - The geometry must be `from.start < to.start < from.end < to.end`. The incoming
61
+ clip must begin inside the outgoing clip and outlast it.
62
+ - A clip may be the `to` of one transition and the `from` of the next, so
63
+ transitions chain. A clip cannot participate twice on the same side.
64
+ - Overlap without a declared transition is still an error. Touching endpoints
65
+ are not a transition.
66
+
67
+ During a dissolve the incoming clip composites above the outgoing clip with
68
+ alpha `clipOpacity × eased progress`. Clip opacity may be static or keyframed
69
+ and multiplies the transition.
70
+
71
+ ## Audio semantics
72
+
73
+ Both kinds use an equal-power ramp over the window: the outgoing clip's gain is
74
+ `sqrt(1 - t)` and the incoming clip's is `sqrt(t)`, after easing. For a
75
+ dissolve, the ramp applies to the audio the two visual clips contribute. For a
76
+ crossfade, it applies to the two audio-track clips themselves.
77
+
78
+ `audio.muted` still zeroes a side, and `audio.gain` plus the track `gain`
79
+ multiply the envelope. A crossfade on an audio track has no visual effect; a
80
+ dissolve on an audio track is rejected.
81
+
82
+ ## What to look at
83
+
84
+ The first dissolve uses `ease-in-out`, so it starts and ends gently around a
85
+ faster middle. The second uses `linear`, so its progress tracks the frame clock
86
+ exactly. The read-only preview draws a hatched band over each overlap, including
87
+ the audio crossfade; the band is informational, not editable.
88
+
89
+ Render or preview with sound. In `transitions` the chords hand off at frames 45
90
+ and 90. In `audio-crossfade` the sound is on the timeline, and the A → C#
91
+ crossfade happens at frames 45..60 while the picture dissolves over the same
92
+ window.
93
+
94
+ ## Files
95
+
96
+ ```text
97
+ examples/transitions/
98
+ assets/alpha.wav A-major chord, 2 s
99
+ assets/beta.wav C#-major chord, 2 s
100
+ assets/gamma.wav E-major chord, 1 s
101
+ compositions/alpha.html outgoing timed card
102
+ compositions/beta.html middle timed card
103
+ compositions/gamma.html incoming timed card
104
+ timelines/transitions.timeline.json
105
+ timelines/audio-crossfade.timeline.json
106
+ fy.config.js
107
+ ```
108
+
109
+ ## Commands
110
+
111
+ From the repository root:
112
+
113
+ ```sh
114
+ pnpm build
115
+ node dist/cli.js check transitions --project examples/transitions
116
+ node dist/cli.js check audio-crossfade --project examples/transitions
117
+ node dist/cli.js still transitions --frame 0,52,60,97 --project examples/transitions --out transitions-stills
118
+ node dist/cli.js still audio-crossfade --frame 52 --project examples/transitions --out audio-crossfade-still.png
119
+ FRAMEYARD_POLICY=full node dist/cli.js render transitions --project examples/transitions
120
+ FRAMEYARD_POLICY=full node dist/cli.js render audio-crossfade --project examples/transitions
121
+ node dist/cli.js dev --project examples/transitions
122
+ ```
123
+
124
+ `render` writes `out/<timeline-id>.mp4` inside the project by default. Frame 52
125
+ sits halfway into the first dissolve, frame 59 is the last frame of that overlap,
126
+ and frame 97 is inside the second dissolve. In `audio-crossfade`, frame 52 is
127
+ also inside the audio crossfade. `still` and `render` share the same composite
128
+ path, so a still taken inside a transition matches the rendered frame.
@@ -0,0 +1,56 @@
1
+ <style>
2
+ :root {
3
+ color-scheme: dark;
4
+ }
5
+ html,
6
+ body {
7
+ margin: 0;
8
+ width: 100%;
9
+ height: 100%;
10
+ background: #0b1220;
11
+ }
12
+ .card {
13
+ position: absolute;
14
+ inset: 0;
15
+ display: grid;
16
+ place-content: center;
17
+ gap: 20px;
18
+ text-align: center;
19
+ color: #e9f1ff;
20
+ font-family:
21
+ ui-sans-serif,
22
+ system-ui,
23
+ -apple-system,
24
+ "Segoe UI",
25
+ sans-serif;
26
+ background:
27
+ radial-gradient(circle at 50% 42%, rgba(88, 140, 255, 0.55), transparent 58%),
28
+ linear-gradient(160deg, #0d1c3a, #0b1220 70%);
29
+ }
30
+ .eyebrow {
31
+ font-size: 20px;
32
+ letter-spacing: 0.42em;
33
+ text-transform: uppercase;
34
+ color: #8fb3ff;
35
+ }
36
+ h1 {
37
+ margin: 0;
38
+ font-size: 148px;
39
+ line-height: 0.92;
40
+ letter-spacing: -0.04em;
41
+ font-weight: 800;
42
+ }
43
+ p {
44
+ margin: 0;
45
+ font-size: 28px;
46
+ color: #9fb0c9;
47
+ }
48
+ </style>
49
+ <fy-composition duration-in-frames="60">
50
+ <div class="card">
51
+ <div class="eyebrow">scene one</div>
52
+ <h1>ALPHA</h1>
53
+ <p>the outgoing clip keeps rendering</p>
54
+ </div>
55
+ <audio src="assets/alpha.wav" data-fy-start="0" data-fy-duration="2"></audio>
56
+ </fy-composition>
@@ -0,0 +1,56 @@
1
+ <style>
2
+ :root {
3
+ color-scheme: dark;
4
+ }
5
+ html,
6
+ body {
7
+ margin: 0;
8
+ width: 100%;
9
+ height: 100%;
10
+ background: #1d1206;
11
+ }
12
+ .card {
13
+ position: absolute;
14
+ inset: 0;
15
+ display: grid;
16
+ place-content: center;
17
+ gap: 20px;
18
+ text-align: center;
19
+ color: #fff5e6;
20
+ font-family:
21
+ ui-sans-serif,
22
+ system-ui,
23
+ -apple-system,
24
+ "Segoe UI",
25
+ sans-serif;
26
+ background:
27
+ radial-gradient(circle at 50% 42%, rgba(255, 168, 66, 0.6), transparent 58%),
28
+ linear-gradient(160deg, #2a1708, #1d1206 70%);
29
+ }
30
+ .eyebrow {
31
+ font-size: 20px;
32
+ letter-spacing: 0.42em;
33
+ text-transform: uppercase;
34
+ color: #ffc078;
35
+ }
36
+ h1 {
37
+ margin: 0;
38
+ font-size: 148px;
39
+ line-height: 0.92;
40
+ letter-spacing: -0.04em;
41
+ font-weight: 800;
42
+ }
43
+ p {
44
+ margin: 0;
45
+ font-size: 28px;
46
+ color: #d8b892;
47
+ }
48
+ </style>
49
+ <fy-composition duration-in-frames="60">
50
+ <div class="card">
51
+ <div class="eyebrow">scene two</div>
52
+ <h1>BETA</h1>
53
+ <p>the incoming clip fades in above it</p>
54
+ </div>
55
+ <audio src="assets/beta.wav" data-fy-start="0" data-fy-duration="2"></audio>
56
+ </fy-composition>
@@ -0,0 +1,56 @@
1
+ <style>
2
+ :root {
3
+ color-scheme: dark;
4
+ }
5
+ html,
6
+ body {
7
+ margin: 0;
8
+ width: 100%;
9
+ height: 100%;
10
+ background: #071a18;
11
+ }
12
+ .card {
13
+ position: absolute;
14
+ inset: 0;
15
+ display: grid;
16
+ place-content: center;
17
+ gap: 20px;
18
+ text-align: center;
19
+ color: #e6fffb;
20
+ font-family:
21
+ ui-sans-serif,
22
+ system-ui,
23
+ -apple-system,
24
+ "Segoe UI",
25
+ sans-serif;
26
+ background:
27
+ radial-gradient(circle at 50% 42%, rgba(64, 224, 190, 0.45), transparent 58%),
28
+ linear-gradient(160deg, #0a2823, #071a18 70%);
29
+ }
30
+ .eyebrow {
31
+ font-size: 20px;
32
+ letter-spacing: 0.42em;
33
+ text-transform: uppercase;
34
+ color: #7cf0d8;
35
+ }
36
+ h1 {
37
+ margin: 0;
38
+ font-size: 148px;
39
+ line-height: 0.92;
40
+ letter-spacing: -0.04em;
41
+ font-weight: 800;
42
+ }
43
+ p {
44
+ margin: 0;
45
+ font-size: 28px;
46
+ color: #9ec9c1;
47
+ }
48
+ </style>
49
+ <fy-composition duration-in-frames="30">
50
+ <div class="card">
51
+ <div class="eyebrow">scene three</div>
52
+ <h1>GAMMA</h1>
53
+ <p>one clip can dissolve in and out</p>
54
+ </div>
55
+ <audio src="assets/gamma.wav" data-fy-start="0" data-fy-duration="1"></audio>
56
+ </fy-composition>
@@ -0,0 +1,12 @@
1
+ export default {
2
+ format: { width: 1280, height: 720, fps: 30 },
3
+ compositions: {
4
+ alpha: "./compositions/alpha.html",
5
+ beta: "./compositions/beta.html",
6
+ gamma: "./compositions/gamma.html",
7
+ },
8
+ timelines: {
9
+ transitions: "./timelines/transitions.timeline.json",
10
+ "audio-crossfade": "./timelines/audio-crossfade.timeline.json",
11
+ },
12
+ };
@@ -0,0 +1,44 @@
1
+ {
2
+ "version": 1,
3
+ "name": "Audio crossfade",
4
+ "tracks": [
5
+ { "id": "v1", "kind": "video" },
6
+ { "id": "a1", "kind": "audio", "gain": 1 }
7
+ ],
8
+ "clips": [
9
+ {
10
+ "id": "alpha",
11
+ "track": "v1",
12
+ "source": { "kind": "composition", "id": "alpha" },
13
+ "start": 0,
14
+ "duration": 60,
15
+ "audio": { "muted": true }
16
+ },
17
+ {
18
+ "id": "beta",
19
+ "track": "v1",
20
+ "source": { "kind": "composition", "id": "beta" },
21
+ "start": 45,
22
+ "duration": 60,
23
+ "audio": { "muted": true }
24
+ },
25
+ {
26
+ "id": "chord-alpha",
27
+ "track": "a1",
28
+ "source": { "kind": "audio", "src": "assets/alpha.wav" },
29
+ "start": 0,
30
+ "duration": 60
31
+ },
32
+ {
33
+ "id": "chord-beta",
34
+ "track": "a1",
35
+ "source": { "kind": "audio", "src": "assets/beta.wav" },
36
+ "start": 45,
37
+ "duration": 60
38
+ }
39
+ ],
40
+ "transitions": [
41
+ { "from": "alpha", "to": "beta", "kind": "dissolve", "easing": "ease-in-out" },
42
+ { "from": "chord-alpha", "to": "chord-beta", "kind": "crossfade", "easing": "linear" }
43
+ ]
44
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "version": 1,
3
+ "tracks": [{ "id": "main", "kind": "video" }],
4
+ "clips": [
5
+ {
6
+ "id": "alpha",
7
+ "track": "main",
8
+ "source": { "kind": "composition", "id": "alpha" },
9
+ "start": 0,
10
+ "duration": 60
11
+ },
12
+ {
13
+ "id": "beta",
14
+ "track": "main",
15
+ "source": { "kind": "composition", "id": "beta" },
16
+ "start": 45,
17
+ "duration": 60
18
+ },
19
+ {
20
+ "id": "gamma",
21
+ "track": "main",
22
+ "source": { "kind": "composition", "id": "gamma" },
23
+ "start": 90,
24
+ "duration": 30
25
+ }
26
+ ],
27
+ "transitions": [
28
+ { "from": "alpha", "to": "beta", "kind": "dissolve", "easing": "ease-in-out" },
29
+ { "from": "beta", "to": "gamma", "kind": "dissolve", "easing": "linear" }
30
+ ]
31
+ }
@@ -92,10 +92,10 @@ node dist/cli.js still video --frame 210 --project examples/video --out video-st
92
92
  node dist/cli.js still video-mp4 --frame 210 --project examples/video --out video-mp4-still.png
93
93
  node dist/cli.js still video-timeline --frame 210 --project examples/video --out video-timeline-still.png
94
94
  node dist/cli.js still video-timeline-mp4 --frame 210 --project examples/video --out video-timeline-mp4-still.png
95
- node dist/cli.js render video --project examples/video --props examples/video/props/video.json
96
- node dist/cli.js render video-mp4 --project examples/video --props examples/video/props/video.json
97
- node dist/cli.js render video-timeline --project examples/video --props examples/video/props/video-timeline.json
98
- node dist/cli.js render video-timeline-mp4 --project examples/video --props examples/video/props/video-timeline.json
95
+ FRAMEYARD_POLICY=full node dist/cli.js render video --project examples/video --props examples/video/props/video.json
96
+ FRAMEYARD_POLICY=full node dist/cli.js render video-mp4 --project examples/video --props examples/video/props/video.json
97
+ FRAMEYARD_POLICY=full node dist/cli.js render video-timeline --project examples/video --props examples/video/props/video-timeline.json
98
+ FRAMEYARD_POLICY=full node dist/cli.js render video-timeline-mp4 --project examples/video --props examples/video/props/video-timeline.json
99
99
  ```
100
100
 
101
101
  Start the interactive preview with: