frameyard 0.8.0 → 0.10.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 (327) hide show
  1. package/CHANGELOG.md +177 -14
  2. package/GLOSSARY.md +112 -2
  3. package/README.md +302 -265
  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 +1685 -93
  21. package/dist/cli.js.map +1 -1
  22. package/dist/composition/format.d.ts +1 -1
  23. package/dist/composition/format.d.ts.map +1 -1
  24. package/dist/composition/format.js +1 -1
  25. package/dist/composition/format.js.map +1 -1
  26. package/dist/context.d.ts +82 -0
  27. package/dist/context.d.ts.map +1 -0
  28. package/dist/context.js +277 -0
  29. package/dist/context.js.map +1 -0
  30. package/dist/contracts.d.ts +25 -0
  31. package/dist/contracts.d.ts.map +1 -1
  32. package/dist/contracts.js.map +1 -1
  33. package/dist/doctor.d.ts +14 -1
  34. package/dist/doctor.d.ts.map +1 -1
  35. package/dist/doctor.js +166 -7
  36. package/dist/doctor.js.map +1 -1
  37. package/dist/errors.d.ts +1 -1
  38. package/dist/errors.d.ts.map +1 -1
  39. package/dist/errors.js.map +1 -1
  40. package/dist/fonts/add.d.ts +41 -0
  41. package/dist/fonts/add.d.ts.map +1 -0
  42. package/dist/fonts/add.js +259 -0
  43. package/dist/fonts/add.js.map +1 -0
  44. package/dist/fonts/binary.d.ts +4 -0
  45. package/dist/fonts/binary.d.ts.map +1 -0
  46. package/dist/fonts/binary.js +21 -0
  47. package/dist/fonts/binary.js.map +1 -0
  48. package/dist/fonts/css.d.ts +4 -0
  49. package/dist/fonts/css.d.ts.map +1 -0
  50. package/dist/fonts/css.js +50 -0
  51. package/dist/fonts/css.js.map +1 -0
  52. package/dist/fonts/fontsource.d.ts +107 -0
  53. package/dist/fonts/fontsource.d.ts.map +1 -0
  54. package/dist/fonts/fontsource.js +565 -0
  55. package/dist/fonts/fontsource.js.map +1 -0
  56. package/dist/fonts/index.d.ts +11 -0
  57. package/dist/fonts/index.d.ts.map +1 -0
  58. package/dist/fonts/index.js +11 -0
  59. package/dist/fonts/index.js.map +1 -0
  60. package/dist/fonts/install.d.ts +24 -0
  61. package/dist/fonts/install.d.ts.map +1 -0
  62. package/dist/fonts/install.js +145 -0
  63. package/dist/fonts/install.js.map +1 -0
  64. package/dist/fonts/manifest.d.ts +21 -0
  65. package/dist/fonts/manifest.d.ts.map +1 -0
  66. package/dist/fonts/manifest.js +96 -0
  67. package/dist/fonts/manifest.js.map +1 -0
  68. package/dist/fonts/options.d.ts +9 -0
  69. package/dist/fonts/options.d.ts.map +1 -0
  70. package/dist/fonts/options.js +66 -0
  71. package/dist/fonts/options.js.map +1 -0
  72. package/dist/fonts/schema.d.ts +4 -0
  73. package/dist/fonts/schema.d.ts.map +1 -0
  74. package/dist/fonts/schema.js +316 -0
  75. package/dist/fonts/schema.js.map +1 -0
  76. package/dist/fonts/summary.d.ts +16 -0
  77. package/dist/fonts/summary.d.ts.map +1 -0
  78. package/dist/fonts/summary.js +22 -0
  79. package/dist/fonts/summary.js.map +1 -0
  80. package/dist/fonts/types.d.ts +56 -0
  81. package/dist/fonts/types.d.ts.map +1 -0
  82. package/dist/fonts/types.js +8 -0
  83. package/dist/fonts/types.js.map +1 -0
  84. package/dist/fonts/verify.d.ts +33 -0
  85. package/dist/fonts/verify.d.ts.map +1 -0
  86. package/dist/fonts/verify.js +200 -0
  87. package/dist/fonts/verify.js.map +1 -0
  88. package/dist/html/validate.d.ts.map +1 -1
  89. package/dist/html/validate.js +59 -0
  90. package/dist/html/validate.js.map +1 -1
  91. package/dist/index.d.ts +3 -3
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +1 -1
  94. package/dist/index.js.map +1 -1
  95. package/dist/inspect.d.ts +61 -0
  96. package/dist/inspect.d.ts.map +1 -0
  97. package/dist/inspect.js +325 -0
  98. package/dist/inspect.js.map +1 -0
  99. package/dist/lint.d.ts +63 -0
  100. package/dist/lint.d.ts.map +1 -0
  101. package/dist/lint.js +265 -0
  102. package/dist/lint.js.map +1 -0
  103. package/dist/policy.d.ts +69 -0
  104. package/dist/policy.d.ts.map +1 -0
  105. package/dist/policy.js +361 -0
  106. package/dist/policy.js.map +1 -0
  107. package/dist/preview/fonts/LICENSE-figtree.txt +93 -0
  108. package/dist/preview/fonts/LICENSE-space-mono.txt +93 -0
  109. package/dist/preview/fonts/README.md +23 -0
  110. package/dist/preview/fonts/figtree-latin-wght-normal.woff2 +0 -0
  111. package/dist/preview/fonts/space-mono-latin-400-normal.woff2 +0 -0
  112. package/dist/preview/fonts/space-mono-latin-700-normal.woff2 +0 -0
  113. package/dist/preview/main.js +5 -2
  114. package/dist/preview/main.js.map +1 -1
  115. package/dist/preview/timeline-data.d.ts +17 -69
  116. package/dist/preview/timeline-data.d.ts.map +1 -1
  117. package/dist/preview/timeline-host.d.ts.map +1 -1
  118. package/dist/preview/timeline-host.js +70 -45
  119. package/dist/preview/timeline-host.js.map +1 -1
  120. package/dist/preview/timeline-main.d.ts.map +1 -1
  121. package/dist/preview/timeline-main.js +88 -27
  122. package/dist/preview/timeline-main.js.map +1 -1
  123. package/dist/project.d.ts +10 -3
  124. package/dist/project.d.ts.map +1 -1
  125. package/dist/project.js +20 -17
  126. package/dist/project.js.map +1 -1
  127. package/dist/renderer/audio.d.ts +6 -0
  128. package/dist/renderer/audio.d.ts.map +1 -1
  129. package/dist/renderer/audio.js +3 -1
  130. package/dist/renderer/audio.js.map +1 -1
  131. package/dist/renderer/browser.d.ts.map +1 -1
  132. package/dist/renderer/browser.js +13 -0
  133. package/dist/renderer/browser.js.map +1 -1
  134. package/dist/renderer/concurrency.d.ts +6 -0
  135. package/dist/renderer/concurrency.d.ts.map +1 -0
  136. package/dist/renderer/concurrency.js +27 -0
  137. package/dist/renderer/concurrency.js.map +1 -0
  138. package/dist/renderer/ffmpeg.d.ts +104 -5
  139. package/dist/renderer/ffmpeg.d.ts.map +1 -1
  140. package/dist/renderer/ffmpeg.js +580 -67
  141. package/dist/renderer/ffmpeg.js.map +1 -1
  142. package/dist/renderer/index.js +1 -1
  143. package/dist/renderer/index.js.map +1 -1
  144. package/dist/renderer/keyframe-expressions.d.ts +39 -0
  145. package/dist/renderer/keyframe-expressions.d.ts.map +1 -0
  146. package/dist/renderer/keyframe-expressions.js +180 -0
  147. package/dist/renderer/keyframe-expressions.js.map +1 -0
  148. package/dist/renderer/session-pool.d.ts +28 -0
  149. package/dist/renderer/session-pool.d.ts.map +1 -0
  150. package/dist/renderer/session-pool.js +91 -0
  151. package/dist/renderer/session-pool.js.map +1 -0
  152. package/dist/renderer/timeline.d.ts +2 -0
  153. package/dist/renderer/timeline.d.ts.map +1 -1
  154. package/dist/renderer/timeline.js +600 -147
  155. package/dist/renderer/timeline.js.map +1 -1
  156. package/dist/renderer/timings.d.ts +13 -0
  157. package/dist/renderer/timings.d.ts.map +1 -0
  158. package/dist/renderer/timings.js +29 -0
  159. package/dist/renderer/timings.js.map +1 -0
  160. package/dist/runtime/host.d.ts.map +1 -1
  161. package/dist/runtime/host.js +4 -0
  162. package/dist/runtime/host.js.map +1 -1
  163. package/dist/scaffold.d.ts +3 -0
  164. package/dist/scaffold.d.ts.map +1 -1
  165. package/dist/scaffold.js +48 -320
  166. package/dist/scaffold.js.map +1 -1
  167. package/dist/server/composition-html.d.ts +2 -0
  168. package/dist/server/composition-html.d.ts.map +1 -1
  169. package/dist/server/composition-html.js +7 -0
  170. package/dist/server/composition-html.js.map +1 -1
  171. package/dist/server/preview-html.d.ts.map +1 -1
  172. package/dist/server/preview-html.js +74 -188
  173. package/dist/server/preview-html.js.map +1 -1
  174. package/dist/server/preview-style.d.ts +11 -0
  175. package/dist/server/preview-style.d.ts.map +1 -0
  176. package/dist/server/preview-style.js +298 -0
  177. package/dist/server/preview-style.js.map +1 -0
  178. package/dist/server/server.d.ts.map +1 -1
  179. package/dist/server/server.js +31 -1
  180. package/dist/server/server.js.map +1 -1
  181. package/dist/server/timeline-preview-data.d.ts.map +1 -1
  182. package/dist/server/timeline-preview-data.js +12 -34
  183. package/dist/server/timeline-preview-data.js.map +1 -1
  184. package/dist/server/timeline-preview-html.d.ts.map +1 -1
  185. package/dist/server/timeline-preview-html.js +140 -148
  186. package/dist/server/timeline-preview-html.js.map +1 -1
  187. package/dist/templates.d.ts +16 -0
  188. package/dist/templates.d.ts.map +1 -0
  189. package/dist/templates.js +186 -0
  190. package/dist/templates.js.map +1 -0
  191. package/dist/timeline/easing.d.ts +36 -0
  192. package/dist/timeline/easing.d.ts.map +1 -0
  193. package/dist/timeline/easing.js +225 -0
  194. package/dist/timeline/easing.js.map +1 -0
  195. package/dist/timeline/keyframes.d.ts +23 -0
  196. package/dist/timeline/keyframes.d.ts.map +1 -0
  197. package/dist/timeline/keyframes.js +95 -0
  198. package/dist/timeline/keyframes.js.map +1 -0
  199. package/dist/timeline/plan.d.ts +6 -12
  200. package/dist/timeline/plan.d.ts.map +1 -1
  201. package/dist/timeline/plan.js +17 -30
  202. package/dist/timeline/plan.js.map +1 -1
  203. package/dist/timeline/resolve.d.ts +12 -0
  204. package/dist/timeline/resolve.d.ts.map +1 -1
  205. package/dist/timeline/resolve.js +105 -9
  206. package/dist/timeline/resolve.js.map +1 -1
  207. package/dist/timeline/scene.d.ts +126 -0
  208. package/dist/timeline/scene.d.ts.map +1 -0
  209. package/dist/timeline/scene.js +109 -0
  210. package/dist/timeline/scene.js.map +1 -0
  211. package/dist/timeline/schema.d.ts +31 -0
  212. package/dist/timeline/schema.d.ts.map +1 -1
  213. package/dist/timeline/schema.js +240 -9
  214. package/dist/timeline/schema.js.map +1 -1
  215. package/dist/timeline/transitions.d.ts +21 -0
  216. package/dist/timeline/transitions.d.ts.map +1 -0
  217. package/dist/timeline/transitions.js +23 -0
  218. package/dist/timeline/transitions.js.map +1 -0
  219. package/dist/version.d.ts +1 -1
  220. package/dist/version.d.ts.map +1 -1
  221. package/dist/version.js +1 -1
  222. package/dist/version.js.map +1 -1
  223. package/docs/adr/0007-make-html-documents-the-composition-unit.md +1 -1
  224. package/docs/adr/0010-make-timelines-the-render-unit.md +3 -2
  225. package/docs/adr/0012-render-timelines-with-segmented-lossless-clip-intermediates.md +1 -1
  226. package/docs/adr/0014-compose-timeline-clips-with-css-blend-modes.md +2 -1
  227. package/docs/adr/0015-inherit-composition-format-from-the-project.md +2 -2
  228. package/docs/adr/0016-author-compositions-as-html-fragments.md +1 -1
  229. package/docs/adr/0018-animate-timeline-clips-with-keyframes.md +35 -0
  230. package/docs/adr/0019-make-same-track-overlap-the-transition-window.md +36 -0
  231. package/docs/adr/0020-crossfade-audio-clips-on-audio-tracks.md +27 -0
  232. package/docs/adr/0021-project-timeline-scenes-once.md +32 -0
  233. package/docs/adr/0022-gate-heavy-operations-with-a-workspace-resource-policy.md +35 -0
  234. package/docs/adr/0023-pin-fonts-as-project-assets.md +98 -0
  235. package/docs/adr/0024-stream-composition-captures-and-skip-trivial-composites.md +86 -0
  236. package/docs/adr/0025-capture-timeline-layers-in-parallel-with-a-global-limit.md +67 -0
  237. package/docs/adr/0026-reuse-loaded-composition-sessions-across-segments.md +70 -0
  238. package/docs/adr/0027-encode-two-full-frame-layers-through-dual-pipes.md +76 -0
  239. package/docs/adr/0028-encode-full-frame-video-segments-directly.md +65 -0
  240. package/docs/migration-0.4.md +2 -2
  241. package/docs/preview-testing.md +27 -6
  242. package/docs/{v0.1-design.md → v0.x-design.md} +57 -32
  243. package/examples/blend/README.md +3 -3
  244. package/examples/demo/README.md +2 -2
  245. package/examples/intro/README.md +71 -0
  246. package/examples/intro/assets/fonts/figtree/LICENSE.txt +93 -0
  247. package/examples/intro/assets/fonts/figtree/figtree-latin-wght-normal.woff2 +0 -0
  248. package/examples/intro/assets/fonts/instrument-serif/LICENSE.txt +93 -0
  249. package/examples/intro/assets/fonts/instrument-serif/instrument-serif-latin-400-italic.woff2 +0 -0
  250. package/examples/intro/assets/fonts/space-mono/LICENSE.txt +93 -0
  251. package/examples/intro/assets/fonts/space-mono/space-mono-latin-400-normal.woff2 +0 -0
  252. package/examples/intro/assets/fonts/space-mono/space-mono-latin-700-normal.woff2 +0 -0
  253. package/examples/intro/assets/grain.png +0 -0
  254. package/examples/intro/assets/score.ogg +0 -0
  255. package/examples/intro/compositions/brand.html +412 -0
  256. package/examples/intro/compositions/chrome.controller.js +63 -0
  257. package/examples/intro/compositions/chrome.html +256 -0
  258. package/examples/intro/compositions/lattice.controller.js +148 -0
  259. package/examples/intro/compositions/lattice.html +308 -0
  260. package/examples/intro/compositions/opening.html +504 -0
  261. package/examples/intro/compositions/studio.html +930 -0
  262. package/examples/intro/compositions/theme.css +218 -0
  263. package/examples/intro/fy.config.js +13 -0
  264. package/examples/intro/fy.fonts.json +100 -0
  265. package/examples/intro/scripts/make-grain.mjs +79 -0
  266. package/examples/intro/scripts/make-score.mjs +702 -0
  267. package/examples/intro/timelines/intro.timeline.json +60 -0
  268. package/examples/keyframes/README.md +76 -0
  269. package/examples/keyframes/compositions/backdrop.html +60 -0
  270. package/examples/keyframes/compositions/card.html +99 -0
  271. package/examples/keyframes/fy.config.js +10 -0
  272. package/examples/keyframes/timelines/keyframes.timeline.json +60 -0
  273. package/examples/nested/README.md +112 -0
  274. package/examples/nested/assets/pulse.wav +0 -0
  275. package/examples/nested/compositions/backdrop.html +84 -0
  276. package/examples/nested/compositions/card.html +275 -0
  277. package/examples/nested/compositions/overlay.html +307 -0
  278. package/examples/nested/fy.config.js +13 -0
  279. package/examples/nested/scripts/make-pulse.sh +20 -0
  280. package/examples/nested/timelines/chapter-a.timeline.json +39 -0
  281. package/examples/nested/timelines/chapter-b.timeline.json +39 -0
  282. package/examples/nested/timelines/nested-reel.timeline.json +52 -0
  283. package/examples/static/README.md +2 -2
  284. package/examples/transitions/README.md +128 -0
  285. package/examples/transitions/assets/alpha.wav +0 -0
  286. package/examples/transitions/assets/beta.wav +0 -0
  287. package/examples/transitions/assets/gamma.wav +0 -0
  288. package/examples/transitions/compositions/alpha.html +56 -0
  289. package/examples/transitions/compositions/beta.html +56 -0
  290. package/examples/transitions/compositions/gamma.html +56 -0
  291. package/examples/transitions/fy.config.js +12 -0
  292. package/examples/transitions/timelines/audio-crossfade.timeline.json +44 -0
  293. package/examples/transitions/timelines/transitions.timeline.json +31 -0
  294. package/examples/video/README.md +4 -4
  295. package/examples/webgl/README.md +1 -1
  296. package/package.json +13 -6
  297. package/scaffolds/init/AGENTS.md +131 -0
  298. package/scaffolds/init/README.md +56 -0
  299. package/scaffolds/init/compositions/hello.html +79 -0
  300. package/scaffolds/init/fy.config.js +9 -0
  301. package/scaffolds/init/gitignore.template +1 -0
  302. package/scaffolds/init/package.json +11 -0
  303. package/scaffolds/init/props/hello.json +4 -0
  304. package/scaffolds/init/props/main.json +6 -0
  305. package/scaffolds/init/timelines/main.timeline.json +20 -0
  306. package/scaffolds/local-models/LOCAL-MODELS.md +77 -0
  307. package/scaffolds/local-models/fy.policy.json +5 -0
  308. package/scaffolds/local-models/opencode/opencode.jsonc +17 -0
  309. package/scaffolds/local-models/pi/APPEND_SYSTEM.md +21 -0
  310. package/scaffolds/local-models/pi/sandbox.json +15 -0
  311. package/scaffolds/local-models/pi/settings.json +15 -0
  312. package/skills/frameyard/README.md +1 -0
  313. package/skills/frameyard/SKILL.md +59 -61
  314. package/skills/frameyard/references/audio-assets.md +32 -3
  315. package/skills/frameyard/references/common-mistakes.md +53 -2
  316. package/skills/frameyard/references/deterministic-authoring.md +10 -3
  317. package/skills/frameyard/references/diagnostics.md +31 -1
  318. package/skills/frameyard/references/failure-recovery.md +32 -6
  319. package/skills/frameyard/references/fonts.md +132 -0
  320. package/skills/frameyard/references/json-results.md +119 -6
  321. package/skills/frameyard/references/quick-reference.md +222 -0
  322. package/skills/frameyard/references/timelines.md +97 -7
  323. /package/examples/blend/{video.config.js → fy.config.js} +0 -0
  324. /package/examples/demo/{video.config.js → fy.config.js} +0 -0
  325. /package/examples/static/{video.config.js → fy.config.js} +0 -0
  326. /package/examples/video/{video.config.js → fy.config.js} +0 -0
  327. /package/examples/webgl/{video.config.js → fy.config.js} +0 -0
@@ -0,0 +1,60 @@
1
+ {
2
+ "version": 1,
3
+ "name": "Frameyard / intro film",
4
+ "durationInFrames": 1200,
5
+ "background": "#06080b",
6
+ "tracks": [
7
+ { "id": "scenes", "kind": "video", "name": "Scenes" },
8
+ { "id": "hud", "kind": "video", "name": "HUD overlay" },
9
+ { "id": "score", "kind": "audio", "name": "Original score" }
10
+ ],
11
+ "clips": [
12
+ {
13
+ "id": "opening",
14
+ "track": "scenes",
15
+ "source": { "kind": "composition", "id": "opening" },
16
+ "start": 0,
17
+ "duration": 300
18
+ },
19
+ {
20
+ "id": "studio",
21
+ "track": "scenes",
22
+ "source": { "kind": "composition", "id": "studio" },
23
+ "start": 270,
24
+ "duration": 330
25
+ },
26
+ {
27
+ "id": "lattice",
28
+ "track": "scenes",
29
+ "source": { "kind": "composition", "id": "lattice" },
30
+ "start": 570,
31
+ "duration": 330
32
+ },
33
+ {
34
+ "id": "brand",
35
+ "track": "scenes",
36
+ "source": { "kind": "composition", "id": "brand" },
37
+ "start": 870,
38
+ "duration": 330
39
+ },
40
+ {
41
+ "id": "chrome",
42
+ "track": "hud",
43
+ "source": { "kind": "composition", "id": "chrome" },
44
+ "start": 0,
45
+ "duration": 1200
46
+ },
47
+ {
48
+ "id": "score",
49
+ "track": "score",
50
+ "source": { "kind": "audio", "src": "assets/score.ogg" },
51
+ "start": 0,
52
+ "duration": 1200
53
+ }
54
+ ],
55
+ "transitions": [
56
+ { "from": "opening", "to": "studio", "kind": "dissolve", "easing": "ease-in-out" },
57
+ { "from": "studio", "to": "lattice", "kind": "dissolve", "easing": "ease-in-out" },
58
+ { "from": "lattice", "to": "brand", "kind": "dissolve", "easing": "ease-in-out" }
59
+ ]
60
+ }
@@ -0,0 +1,76 @@
1
+ # Timeline keyframes example
2
+
3
+ A 6-second, 1280×720, 30 fps timeline that animates one clip through every
4
+ supported presentation channel:
5
+
6
+ - `v1` **backdrop** — a static HTML composition with a gradient, a perspective
7
+ grid, and a soft glow. It declares no time metadata, so it is captured once
8
+ as an opaque image layer.
9
+ - `v2` **card** — a static transparent HTML composition captured once as well.
10
+ Its timeline clip uses `blend: "screen"` and keyframes on `x`, `y`, `scale`,
11
+ `rotation`, and `opacity`.
12
+
13
+ The card enters from the left, settles in the center, holds, and leaves toward
14
+ the right while it fades. Position, scale, and rotation use `ease-out` into the
15
+ hold and `ease-in` out of it. The clip uses `ease-out` and `ease-in` rather than
16
+ animating inside the card, because this animation belongs to the edit, not to
17
+ the reusable composition.
18
+
19
+ ## Keyframe contract
20
+
21
+ `keyframes` is a per-clip object keyed by channel. Each channel is an array of
22
+ `{ "frame", "value", "easing" }` entries:
23
+
24
+ - `frame` is clip-local. `0` is the clip start; `duration` is the end boundary.
25
+ - Keys must be integers in `0..duration`, strictly increasing, with at least two
26
+ entries per channel.
27
+ - A channel holds its first value before the first key and its last value after
28
+ the last key.
29
+ - `easing` applies to the interval after that key and defaults to `linear`. The
30
+ supported vocabulary is `linear`, `ease`, `ease-in`, `ease-out`,
31
+ `ease-in-out`, `step-start`, `step-end`, `hold`, `cubic-bezier(...)`, and
32
+ `steps(...)`. The final key’s easing has no effect.
33
+ - A channel cannot also be an explicit scalar in `transform`; keyed channels
34
+ and static channels are separate. Defaults do not conflict.
35
+ - `opacity` is `0..1`, `scale` is `>= 0`, and interpolated opacity and scale are
36
+ clamped after easing. Position and rotation may overshoot.
37
+ - `keyframes.scale` cannot be combined with a `transform.width`/`height` box.
38
+
39
+ Preview evaluates the CSS timing functions exactly and seeks the clip styles on
40
+ every frame. Production generates FFmpeg expressions from the same evaluator,
41
+ using an alpha mask for animated opacity. Floating-point easing in the filter
42
+ graph is an approximation of the CSS curve; parity is guaranteed at clip capture,
43
+ not at the final encoded pixel. Fractional positions can differ by a subpixel.
44
+
45
+ ## Files
46
+
47
+ ```text
48
+ examples/keyframes/
49
+ compositions/backdrop.html opaque static backdrop
50
+ compositions/card.html transparent static card
51
+ timelines/keyframes.timeline.json
52
+ fy.config.js
53
+ ```
54
+
55
+ The timeline lives in `timelines/keyframes.timeline.json`. Both compositions are
56
+ static: they declare neither `duration-in-frames` nor `fps`, and the timeline
57
+ supplies their durations and `sourceIn: 0`. The card is still animated on the
58
+ timeline because keyframes are a clip property, not a composition property.
59
+
60
+ ## Commands
61
+
62
+ From the repository root:
63
+
64
+ ```sh
65
+ pnpm build
66
+ node dist/cli.js check keyframes --project examples/keyframes
67
+ node dist/cli.js still keyframes --frame 12,90,170 --project examples/keyframes --out keyframes-stills
68
+ node dist/cli.js still keyframes --frame 90 --project examples/keyframes --out keyframes-center.png
69
+ FRAMEYARD_POLICY=full node dist/cli.js render keyframes --project examples/keyframes
70
+ node dist/cli.js dev --project examples/keyframes
71
+ ```
72
+
73
+ `render` writes `out/keyframes.mp4` inside the project by default. The batch
74
+ still captures the entrance, the hold, and the exit through the same clip
75
+ capture and composite path as the render. `dev` lists the timeline and shows
76
+ the keyframe markers on the clip bar; clicking a marker seeks to that frame.
@@ -0,0 +1,60 @@
1
+ <style>
2
+ html,
3
+ body {
4
+ margin: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ overflow: hidden;
8
+ background: #080b10;
9
+ }
10
+ .backdrop {
11
+ position: absolute;
12
+ inset: 0;
13
+ background:
14
+ radial-gradient(circle at 24% 18%, rgba(95, 214, 255, 0.22), transparent 34%),
15
+ radial-gradient(circle at 78% 76%, rgba(184, 243, 107, 0.16), transparent 32%),
16
+ linear-gradient(145deg, #0a1018 0%, #0b1119 52%, #070a0f 100%);
17
+ }
18
+ .grid {
19
+ position: absolute;
20
+ inset: -20%;
21
+ background-image:
22
+ linear-gradient(rgba(120, 150, 180, 0.08) 1px, transparent 1px),
23
+ linear-gradient(90deg, rgba(120, 150, 180, 0.08) 1px, transparent 1px);
24
+ background-size: 48px 48px;
25
+ transform: perspective(900px) rotateX(62deg) translateY(18%);
26
+ transform-origin: center bottom;
27
+ }
28
+ .glow {
29
+ position: absolute;
30
+ width: 520px;
31
+ height: 520px;
32
+ left: 50%;
33
+ top: 50%;
34
+ transform: translate(-50%, -50%);
35
+ border-radius: 50%;
36
+ background: radial-gradient(circle, rgba(184, 243, 107, 0.14), transparent 64%);
37
+ }
38
+ .title {
39
+ position: absolute;
40
+ left: 56px;
41
+ bottom: 48px;
42
+ color: #dce8f5;
43
+ font:
44
+ 620 22px/1.2 Inter,
45
+ ui-sans-serif,
46
+ system-ui,
47
+ -apple-system,
48
+ "Segoe UI",
49
+ sans-serif;
50
+ letter-spacing: 0.16em;
51
+ text-transform: uppercase;
52
+ opacity: 0.82;
53
+ }
54
+ </style>
55
+ <fy-composition width="1280" height="720">
56
+ <div class="backdrop"></div>
57
+ <div class="grid"></div>
58
+ <div class="glow"></div>
59
+ <div class="title">Timeline keyframes</div>
60
+ </fy-composition>
@@ -0,0 +1,99 @@
1
+ <style>
2
+ html,
3
+ body {
4
+ margin: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ overflow: hidden;
8
+ background: transparent;
9
+ }
10
+ .card {
11
+ position: absolute;
12
+ inset: 0;
13
+ border: 1px solid rgba(184, 243, 107, 0.5);
14
+ border-radius: 28px;
15
+ background:
16
+ linear-gradient(135deg, rgba(184, 243, 107, 0.18), rgba(95, 214, 255, 0.06) 54%, rgba(8, 11, 16, 0.9)),
17
+ rgba(10, 16, 24, 0.82);
18
+ box-shadow:
19
+ 0 0 0 1px rgba(184, 243, 107, 0.08) inset,
20
+ 0 42px 120px rgba(0, 0, 0, 0.55);
21
+ overflow: hidden;
22
+ }
23
+ .card::before {
24
+ content: "";
25
+ position: absolute;
26
+ inset: -40% -10%;
27
+ background: radial-gradient(circle at 22% 26%, rgba(184, 243, 107, 0.32), transparent 36%);
28
+ }
29
+ .eyebrow {
30
+ position: absolute;
31
+ left: 48px;
32
+ top: 44px;
33
+ color: #b8f36b;
34
+ font:
35
+ 600 15px/1 Inter,
36
+ ui-sans-serif,
37
+ system-ui,
38
+ sans-serif;
39
+ letter-spacing: 0.2em;
40
+ text-transform: uppercase;
41
+ }
42
+ .heading {
43
+ position: absolute;
44
+ left: 48px;
45
+ right: 48px;
46
+ top: 92px;
47
+ color: #f2f7fb;
48
+ font:
49
+ 680 64px/1.02 Inter,
50
+ ui-sans-serif,
51
+ system-ui,
52
+ sans-serif;
53
+ letter-spacing: -0.035em;
54
+ }
55
+ .rule {
56
+ position: absolute;
57
+ left: 48px;
58
+ right: 48px;
59
+ bottom: 96px;
60
+ height: 1px;
61
+ background: linear-gradient(90deg, rgba(184, 243, 107, 0.6), rgba(95, 214, 255, 0.18), transparent);
62
+ }
63
+ .meta {
64
+ position: absolute;
65
+ left: 48px;
66
+ bottom: 52px;
67
+ color: rgba(220, 232, 245, 0.72);
68
+ font:
69
+ 500 18px/1.4 Inter,
70
+ ui-sans-serif,
71
+ system-ui,
72
+ sans-serif;
73
+ }
74
+ .badge {
75
+ position: absolute;
76
+ right: 48px;
77
+ bottom: 48px;
78
+ padding: 10px 16px;
79
+ border: 1px solid rgba(184, 243, 107, 0.44);
80
+ border-radius: 999px;
81
+ color: #b8f36b;
82
+ font:
83
+ 600 14px/1 Inter,
84
+ ui-sans-serif,
85
+ system-ui,
86
+ sans-serif;
87
+ letter-spacing: 0.12em;
88
+ text-transform: uppercase;
89
+ }
90
+ </style>
91
+ <fy-composition width="640" height="360">
92
+ <div class="card">
93
+ <div class="eyebrow">Frameyard</div>
94
+ <div class="heading">Animate the edit,<br />not the composition.</div>
95
+ <div class="rule"></div>
96
+ <div class="meta">One clip. Five channels. Four keys.</div>
97
+ <div class="badge">Keyframes</div>
98
+ </div>
99
+ </fy-composition>
@@ -0,0 +1,10 @@
1
+ export default {
2
+ format: { width: 1280, height: 720, fps: 30 },
3
+ compositions: {
4
+ backdrop: "./compositions/backdrop.html",
5
+ card: "./compositions/card.html",
6
+ },
7
+ timelines: {
8
+ keyframes: "./timelines/keyframes.timeline.json",
9
+ },
10
+ };
@@ -0,0 +1,60 @@
1
+ {
2
+ "version": 1,
3
+ "name": "Keyframes",
4
+ "background": "#080b10",
5
+ "tracks": [
6
+ { "id": "v1", "kind": "video" },
7
+ { "id": "v2", "kind": "video" }
8
+ ],
9
+ "clips": [
10
+ {
11
+ "id": "backdrop",
12
+ "track": "v1",
13
+ "source": { "kind": "composition", "id": "backdrop" },
14
+ "start": 0,
15
+ "duration": 180,
16
+ "sourceIn": 0
17
+ },
18
+ {
19
+ "id": "card",
20
+ "track": "v2",
21
+ "source": { "kind": "composition", "id": "card" },
22
+ "start": 0,
23
+ "duration": 180,
24
+ "sourceIn": 0,
25
+ "blend": "screen",
26
+ "keyframes": {
27
+ "opacity": [
28
+ { "frame": 0, "value": 0 },
29
+ { "frame": 24, "value": 1, "easing": "ease-out" },
30
+ { "frame": 150, "value": 1 },
31
+ { "frame": 180, "value": 0, "easing": "ease-in" }
32
+ ],
33
+ "x": [
34
+ { "frame": 0, "value": -120, "easing": "ease-out" },
35
+ { "frame": 24, "value": 0 },
36
+ { "frame": 150, "value": 0 },
37
+ { "frame": 180, "value": 120, "easing": "ease-in" }
38
+ ],
39
+ "y": [
40
+ { "frame": 0, "value": 48, "easing": "ease-out" },
41
+ { "frame": 24, "value": 0 },
42
+ { "frame": 150, "value": 0 },
43
+ { "frame": 180, "value": -32, "easing": "ease-in" }
44
+ ],
45
+ "scale": [
46
+ { "frame": 0, "value": 0.42, "easing": "ease-out" },
47
+ { "frame": 24, "value": 0.5 },
48
+ { "frame": 150, "value": 0.5 },
49
+ { "frame": 180, "value": 0.46, "easing": "ease-in" }
50
+ ],
51
+ "rotation": [
52
+ { "frame": 0, "value": -8, "easing": "ease-out" },
53
+ { "frame": 24, "value": 0 },
54
+ { "frame": 150, "value": 0 },
55
+ { "frame": 180, "value": 8, "easing": "ease-in" }
56
+ ]
57
+ }
58
+ }
59
+ ]
60
+ }
@@ -0,0 +1,112 @@
1
+ # Frameyard nested timelines example
2
+
3
+ A 12-second, 1280×720, 30 fps outer timeline that places two reusable inner
4
+ timelines on its tracks:
5
+
6
+ | Frames | Placement | What plays |
7
+ | --- | --- | --- |
8
+ | 0–119 | `chapter-a` · full frame | Chapter A on track `v1` |
9
+ | 120–239 | `chapter-b` · full frame | Chapter B on track `v2` |
10
+ | 240–359 | `chapter-a` · 480×270 group | Chapter A again on track `v3`, muted |
11
+
12
+ `chapter-a` and `chapter-b` are complete timelines in their own right. Each
13
+ contains a static backdrop, an animated card composition with its own props, and
14
+ a local audio clip. The outer timeline references them with
15
+ `"source": { "kind": "timeline", "id": "chapter-a" }` instead of referencing
16
+ their compositions directly.
17
+
18
+ The third placement repeats `chapter-a` with a group transform:
19
+
20
+ ```json
21
+ {
22
+ "source": { "kind": "timeline", "id": "chapter-a" },
23
+ "start": 240,
24
+ "duration": 120,
25
+ "transform": {
26
+ "fit": "cover",
27
+ "width": 480,
28
+ "height": 270,
29
+ "x": 360,
30
+ "y": 180
31
+ },
32
+ "audio": { "muted": true }
33
+ }
34
+ ```
35
+
36
+ The transform applies to the whole nested result. Every layer inside the chapter
37
+ moves, scales, and clips together; the parent clip does not reach into the child
38
+ timeline. The same rule applies to opacity and blend mode. Nested audio is mixed
39
+ with the outer timeline automatically, so the final placement sets
40
+ `audio.muted` to avoid replaying the pulse.
41
+
42
+ ## Layers inside the chapters
43
+
44
+ - `backdrop` is a static opaque composition. It declares no dimensions, no
45
+ `fps`, and no `duration-in-frames`, so it inherits the project format. The
46
+ renderer captures it once and reuses that PNG across segments and both chapter
47
+ timelines.
48
+ - `card` is a 120-frame animated composition. The chapter timeline places it
49
+ with props for the chapter number, copy, and accent color. The card paints on
50
+ transparent alpha above the backdrop; its progress bar reads
51
+ `--fy-progress` and the dial runs on the local frame clock.
52
+ - `pulse` is a 1.8-second WAV on the chapter's audio track. In the outer
53
+ timeline the first two chapter clips contribute it; `check` reports two mixed
54
+ audio clips.
55
+
56
+ `overlay` is a top-level composition on track `v4`. It draws the project chrome,
57
+ the frame counter, and the global progress bar for the full 12 seconds. Its PiP
58
+ border and left-hand caption are scheduled with
59
+ `data-fy-anim-start="240"`/`"246"` and an `animation` with `both` fill, so they
60
+ stay invisible until the grouped replay begins.
61
+
62
+ ## Files
63
+
64
+ ```text
65
+ examples/nested/
66
+ compositions/backdrop.html static inherited-format backdrop
67
+ compositions/card.html animated transparent card
68
+ compositions/overlay.html project chrome, PiP border, and outro caption
69
+ timelines/chapter-a.timeline.json
70
+ timelines/chapter-b.timeline.json
71
+ timelines/nested-reel.timeline.json
72
+ assets/pulse.wav local chapter audio
73
+ scripts/make-pulse.sh regenerate assets/pulse.wav
74
+ fy.config.js
75
+ ```
76
+
77
+ `chapter-a` and `chapter-b` are listed in `fy.config.js`, so each can be
78
+ checked, previewed, stilled, or rendered on its own. Timeline `--props` is keyed
79
+ by top-level composition clip ID; a nested timeline clip does not forward prop
80
+ overrides to the compositions inside it. Fix chapter props in the chapter's own
81
+ timeline document.
82
+
83
+ ## Commands
84
+
85
+ From the repository root:
86
+
87
+ ```sh
88
+ pnpm build
89
+ node dist/cli.js check chapter-a --project examples/nested
90
+ node dist/cli.js check nested-reel --project examples/nested
91
+ node dist/cli.js still nested-reel --frame 60,180,300 --project examples/nested --out nested-stills
92
+ FRAMEYARD_POLICY=full node dist/cli.js render chapter-a --project examples/nested
93
+ FRAMEYARD_POLICY=full node dist/cli.js render nested-reel --project examples/nested
94
+ node dist/cli.js dev --project examples/nested
95
+ ```
96
+
97
+ The batch still captures chapter A full frame, chapter B full frame, and the
98
+ grouped replay with its overlay. `render` writes `out/chapter-a.mp4` and
99
+ `out/nested-reel.mp4` inside the project by default; pass `--out` for another
100
+ path. The outer render uses the segmented lossless pipeline and captures the
101
+ nested ranges through the same clip path as the stills. Use `--workers` and
102
+ `--scratch` to tune timeline rendering.
103
+
104
+ Regenerate the pulse with FFmpeg:
105
+
106
+ ```sh
107
+ bash examples/nested/scripts/make-pulse.sh
108
+ ```
109
+
110
+ Pass an output path as the first argument to write elsewhere. The source is a
111
+ pair of faded sine tones; replace `assets/pulse.wav` with any local WAV, MP3,
112
+ M4A, AAC, or video file if you want different audio.
Binary file
@@ -0,0 +1,84 @@
1
+ <style>
2
+ html,
3
+ body {
4
+ margin: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ background: #05070d;
8
+ }
9
+ fy-composition {
10
+ display: block;
11
+ width: 100%;
12
+ height: 100%;
13
+ overflow: hidden;
14
+ }
15
+ .scene {
16
+ position: relative;
17
+ width: 100%;
18
+ height: 100%;
19
+ overflow: hidden;
20
+ background:
21
+ radial-gradient(980px 680px at 76% 22%, rgba(43, 104, 210, 0.42), transparent 63%),
22
+ radial-gradient(760px 520px at 12% 88%, rgba(255, 158, 74, 0.13), transparent 66%),
23
+ linear-gradient(148deg, #07101d 0%, #0a1628 50%, #05070d 100%);
24
+ }
25
+ .grid {
26
+ position: absolute;
27
+ inset: -1px;
28
+ background-image:
29
+ linear-gradient(rgba(154, 192, 255, 0.07) 1px, transparent 1px),
30
+ linear-gradient(90deg, rgba(154, 192, 255, 0.07) 1px, transparent 1px);
31
+ background-size: 64px 64px;
32
+ -webkit-mask-image: radial-gradient(ellipse at 50% 44%, #000 20%, transparent 78%);
33
+ mask-image: radial-gradient(ellipse at 50% 44%, #000 20%, transparent 78%);
34
+ }
35
+ .ring {
36
+ position: absolute;
37
+ right: -140px;
38
+ top: 50%;
39
+ width: 720px;
40
+ height: 720px;
41
+ border: 1px solid rgba(160, 205, 255, 0.12);
42
+ border-radius: 50%;
43
+ transform: translateY(-50%);
44
+ }
45
+ .ring::before,
46
+ .ring::after {
47
+ content: "";
48
+ position: absolute;
49
+ border: 1px solid rgba(255, 179, 71, 0.09);
50
+ border-radius: 50%;
51
+ }
52
+ .ring::before {
53
+ inset: 110px;
54
+ }
55
+ .ring::after {
56
+ inset: 230px;
57
+ border-color: rgba(127, 214, 255, 0.08);
58
+ }
59
+ .beam {
60
+ position: absolute;
61
+ left: 58%;
62
+ top: -20%;
63
+ width: 1px;
64
+ height: 140%;
65
+ background: linear-gradient(180deg, transparent, rgba(127, 214, 255, 0.16), transparent);
66
+ transform: rotate(18deg);
67
+ }
68
+ .vignette {
69
+ position: absolute;
70
+ inset: 0;
71
+ background:
72
+ radial-gradient(ellipse at 52% 44%, transparent 30%, rgba(5, 7, 13, 0.4) 72%, rgba(5, 7, 13, 0.85) 100%),
73
+ linear-gradient(90deg, rgba(5, 7, 13, 0.35), transparent 28%, transparent 72%, rgba(5, 7, 13, 0.4));
74
+ pointer-events: none;
75
+ }
76
+ </style>
77
+ <fy-composition>
78
+ <div class="scene">
79
+ <div class="grid"></div>
80
+ <div class="ring"></div>
81
+ <div class="beam"></div>
82
+ <div class="vignette"></div>
83
+ </div>
84
+ </fy-composition>