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
package/CHANGELOG.md CHANGED
@@ -8,6 +8,170 @@ Frameyard uses `0.x` versions until the composition, configuration, CLI,
8
8
  JSON-result, transcript, and skill contracts stabilize. Before `1.0.0`, a
9
9
  contract change may ship in a minor release and is called out explicitly below.
10
10
 
11
+ ## [Unreleased]
12
+
13
+ ## [0.9.0] - 2026-11-06
14
+
15
+ ### Added
16
+
17
+ - `fy.fonts.json` records vendored font families, files, licenses, and
18
+ SHA-256 values. `frameyard font search` filters the Fontsource catalog, and
19
+ `font add <id>` pins a Fontsource `npmVersion`, vendors its WOFF2 files and
20
+ license, and supports variable `wght` or explicit static weights. `font add
21
+ --file` copies a local WOFF2/WOFF/TTF/OTF font, `font add --url` vendors an
22
+ HTTPS source with an optional SHA-256, and `font list`/`verify`/`remove`
23
+ manage the manifest. The server injects the generated `@font-face` sheet into
24
+ every composition, the host fails with `FONT_LOAD_FAILED` when a used face
25
+ does not load, and static validation warns with `FONT_REMOTE` for remote font
26
+ URLs. See
27
+ `docs/adr/0023-pin-fonts-as-project-assets.md`.
28
+ - `fy.policy.json` is a workspace resource policy discovered from the
29
+ working directory upward. It gates `tts`, `transcribe`, and `render`, caps
30
+ `still`, denies `--force`/`--overwrite`, and supports the `shared-cpu`
31
+ profile. `FRAMEYARD_POLICY` overrides the file. See
32
+ `docs/adr/0022-gate-heavy-operations-with-a-workspace-resource-policy.md`.
33
+ - `frameyard lint <id>` runs static validation, timeline resolution, scene and
34
+ keyframe projection for every frame, a controller determinism scan, and media
35
+ metadata probing without Chromium. It is the edit-loop gate; `check` remains
36
+ the full gate.
37
+ - `frameyard context --json` prints a compact project digest with resolved
38
+ composition metadata, prop keys and types, timeline clip summaries, unique
39
+ assets, diagnostics, the active policy, and next commands.
40
+ - `frameyard inspect <image> --json` reports dimensions, alpha coverage, blank
41
+ detection, background and dominant colors, a content bounding box, a coarse
42
+ luminance grid, and edge density. `frameyard compare <a> <b> --json` reports
43
+ pixel and 3×3 region differences and exits 1 when the images differ.
44
+ - `frameyard add --list` and `frameyard add <composition|timeline> <id>
45
+ --template <name>` write `static-poster`, `title-card`, `lower-third`, or
46
+ `slideshow` entries and print the exact `fy.config.js` registration
47
+ snippet. `add local-models` and `init --local-models` install the optional
48
+ policy, OpenCode permissions, and Pi settings. `add` never edits the config
49
+ and never overwrites an existing entry. The skill adds a compact quick
50
+ reference for limited-context hosts.
51
+ - `init` templates now live under `scaffolds/init/`; `src/scaffold.ts` reads
52
+ them and renders the project-name and version placeholders.
53
+ - `pnpm eval:local` runs an opt-in local-model authoring eval against a
54
+ llama.cpp router. It covers eight tasks, feeds `quick-reference.md` into the
55
+ system prompt, repairs lint failures, and reports first-pass status, turns,
56
+ tokens, wall time, sampled server CPU/RSS/GPU, and optional budgets. It never
57
+ renders and stays outside `pnpm check`.
58
+ - `tts`, `transcribe`, and `render` accept `--dry-run`, which validates inputs
59
+ and returns `plan`, `commandLine`, `prerequisites`, and `blockers` without
60
+ writing outputs, loading a model, launching Chromium, or encoding.
61
+ `--profile shared-cpu` caps timeline workers at 2 and composition concurrency
62
+ at 1; `--low-priority` lowers process priority without the caps.
63
+ - `doctor` reports the active resource policy and basic CPU, load, and memory
64
+ resources. Local-model host setup is opt-in through
65
+ `scaffolds/local-models/`, which contains the `authoring` policy, OpenCode
66
+ permissions, and Pi settings; the repository does not enable it by default.
67
+ - `tts --engine supertonic` synthesizes 31-language speech through
68
+ `sherpa-onnx-offline-tts` and the Supertonic 3 model archive. It normalizes
69
+ the engine output to the frozen 24 kHz mono WAV contract, records the seven
70
+ model files in the provenance manifest, and adds `doctor --sherpa-onnx-tts`
71
+ and `doctor --model-dir` inspection. The default engine remains `qwen`.
72
+ - Timeline clips on one video track may dissolve across their overlap through a
73
+ timeline-level `transitions` array. The window is `to.start .. from.end`; the
74
+ incoming clip composites above the outgoing clip with `opacity × eased
75
+ progress`, contributed audio crossfades with equal power, and preview draws a
76
+ read-only band. Undeclared overlap stays an error. See
77
+ `docs/adr/0019-make-same-track-overlap-the-transition-window.md` and
78
+ `examples/transitions/`.
79
+ - Audio clips on an audio track may crossfade through the same `transitions`
80
+ array with `kind: "crossfade"`. The kind/track matrix is explicit: `dissolve`
81
+ is video-only, `crossfade` is audio-only. See
82
+ `docs/adr/0020-crossfade-audio-clips-on-audio-tracks.md` and the
83
+ `audio-crossfade` timeline in `examples/transitions/`.
84
+ - Timeline clips animate `x`, `y`, `scale`, `rotation`, and `opacity` with
85
+ clip-local `keyframes`, CSS easing, preview markers, and FFmpeg expressions.
86
+ Animated opacity composes through blend modes. See
87
+ `docs/adr/0018-animate-timeline-clips-with-keyframes.md` and
88
+ `examples/keyframes/`.
89
+
90
+ ### Changed
91
+
92
+ - `frameyard init` without a directory prompts for a project name (default
93
+ `my-video`) on an interactive terminal and creates a directory with that
94
+ name. Non-interactive and `--json` runs use `my-video` without prompting.
95
+ Pass a directory to scaffold into an explicit path or the current directory.
96
+ - The project configuration file is `fy.config.js` (previously
97
+ `video.config.js`), the assets manifest is `fy.assets.json` (previously
98
+ `frameyard.assets.json`), and the workspace resource policy is
99
+ `fy.policy.json` (previously `frameyard.policy.json`). Rename existing files
100
+ in place; the loaders read only the new names. `FRAMEYARD_POLICY` keeps
101
+ its name.
102
+
103
+ ### Fixed
104
+
105
+ - Timeline preview layers pin `color-scheme: light` on the embedded document
106
+ root. A composition that declared a dark scheme could paint an opaque canvas
107
+ base and hide every layer beneath it, so a timeline with a transparent
108
+ overlay composition appeared to show only the overlay.
109
+ - Rotated timeline clips keep their full corners. Keyed rotation uses a
110
+ segment-stable diagonal bound; static rotation uses its exact rotated
111
+ bounding box.
112
+ - Animated-opacity masks use a 2×2 FFmpeg source instead of 1×1. FFmpeg 6
113
+ rejects a 1×1 `yuv420p` frame, which broke keyframe, transition, and example
114
+ renders on Ubuntu 24.04 runners.
115
+
116
+ [0.9.0]: https://github.com/vladiantio/frameyard/compare/v0.8.0...v0.9.0
117
+
118
+ ## [0.8.0] - 2026-11-03
119
+
120
+ ### Changed
121
+
122
+ - Compositions are partial HTML fragments instead of full HTML documents.
123
+ Authors keep one top-level `<fy-composition>` root plus top-level props,
124
+ controller, import maps, and styles; Frameyard generates the document shell,
125
+ import map, sizing style, format block, and host runtime. A legacy full
126
+ document fails with `COMPOSITION_DOCUMENT_SHELL`. See ADR 0016 and
127
+ [migration-0.8](./docs/migration-0.8.md).
128
+ - Top-level author import maps are validated and hoisted after the framework
129
+ map; `frameyard` and `frameyard/` specifiers are reserved and invalid maps
130
+ report `IMPORT_MAP_INVALID`.
131
+ - `<fy-composition>` makes `width`, `height`, and `fps` optional and keeps
132
+ `duration-in-frames` required only for an animated composition. An omitted
133
+ value is inherited from the project `format`; a declared value wins, and
134
+ `width`/`height` must be declared together. `CompositionSourceInfo` gains
135
+ `declaredFormat`, and `project.json`/render results keep exposing resolved
136
+ metadata.
137
+ - A composition that declares neither `duration-in-frames` nor `fps` is static.
138
+ It has no frame clock, is captured once, and `render` writes `out/<id>.png`.
139
+ `fps` without `duration-in-frames` is an error. See ADR 0017 and
140
+ [migration-0.8](./docs/migration-0.8.md).
141
+ - Static compositions are visual-only: `data-fy-anim-*`, `<video>`, `<audio>`,
142
+ and `<fy-transcript>` are errors. The host runs `mount()` and prop validation,
143
+ skips `update()`, leaves frame-state variables unset, pauses CSS animations at
144
+ time 0, and reports `STATIC_UPDATE_IGNORED` and `STATIC_ANIMATION_PAUSED`.
145
+ - A static composition clip in a timeline behaves like an image clip: it
146
+ requires `duration`, rejects nonzero `sourceIn`, contributes no audio or
147
+ transcripts, and is captured once per composition/props pair and reused
148
+ across segments.
149
+ - `render` results are a discriminated union with `kind: "video"` or
150
+ `kind: "image"`. Static composition renders reject `--container`, `--codec`,
151
+ `--encoder`, and `--concurrency`. `check` adds `kind: "animated"` or
152
+ `kind: "static"`; a static check inspects one state and reports
153
+ `framesChecked: 1`.
154
+ - `still` on a static composition exposes exactly frame 0; other frames fail
155
+ with `FRAME_OUT_OF_RANGE`.
156
+ - Preview loads a static composition once, disables its transport, and keeps a
157
+ static timeline layer loaded without seeking it.
158
+ - Rendering a composition that omits dimensions in a project without `format`
159
+ fails with `COMPOSITION_FORMAT_UNRESOLVED` instead of a document diagnostic;
160
+ `check`, `still`, `render`, and the preview report the same actionable error.
161
+ See ADR 0015.
162
+
163
+ ### Added
164
+
165
+ - Visual timeline clips accept a static `blend` mode with the 12 separable CSS
166
+ modes (`normal` through `exclusion`); `hue`, `saturation`, `color`, and
167
+ `luminosity` are rejected because they need cross-channel math. Preview
168
+ applies `mix-blend-mode` and stacks layers by track; production uses an
169
+ alpha-aware FFmpeg composite. Nested timelines blend as flattened groups.
170
+ See ADR 0014 and `examples/blend/`.
171
+ - Static composition validation, host, CLI, timeline, and preview coverage.
172
+
173
+ [0.8.0]: https://github.com/vladiantio/frameyard/compare/v0.7.0...v0.8.0
174
+
11
175
  ## [0.7.0] - 2026-11-02
12
176
 
13
177
  ### Added
@@ -81,8 +245,6 @@ contract change may ship in a minor release and is called out explicitly below.
81
245
  `timelines` map. `project.json` publishes `format` and `timelines`.
82
246
  - Timeline-first `init` scaffold with a composition clip, timeline override
83
247
  props, and `main` commands.
84
- - The video example adds WebM and MP4 timelines that place video clips under a
85
- transparent HTML overlay.
86
248
  - Official skill reference for timelines, clips, tracks, nested timelines,
87
249
  props overrides, commands, preview, and determinism.
88
250
  - Timeline config, schema, resolution, planning, rendering, preview, and
@@ -112,11 +274,6 @@ contract change may ship in a minor release and is called out explicitly below.
112
274
 
113
275
  ### Added
114
276
 
115
- - `examples/webgl/` demonstrates a frame-seeked WebGL shader driven by an
116
- external controller.
117
- - `examples/video/` demonstrates two-pass embedded video with the same
118
- composition rendered from WebM and MP4 sources, plus a script that
119
- regenerates both clips with FFmpeg.
120
277
  - `test/examples.test.ts` captures a still from every bundled example.
121
278
  - `pnpm compare-stills <a.png> <b.png>` reports an exact decoded-pixel diff and
122
279
  exits nonzero on any difference, for checking determinism at the capture
package/GLOSSARY.md CHANGED
@@ -11,27 +11,39 @@ A collection of related compositions, timelines, and the assets and configuratio
11
11
  _Avoid_: Video, render
12
12
 
13
13
  **Project format**:
14
- The fixed width, height, and frame rate shared by every timeline in a project.
14
+ The fixed width, height, and frame rate shared by every timeline in a project and inherited by compositions that omit those values.
15
15
  _Avoid_: Composition metadata, render options
16
16
 
17
17
  **Composition**:
18
- A uniquely identified, reusable video source with fixed dimensions, frame rate, duration in frames, and content parameterized by props.
18
+ A uniquely identified, reusable video or still source, content parameterized by props, and a format that is either declared or inherited from the project. An animated composition has a duration in frames; a static composition has no time base.
19
19
  _Avoid_: Scene, template, video
20
20
 
21
+ **Static composition**:
22
+ A composition that declares neither `duration-in-frames` nor `fps`. It has no frame clock, audio, or transcripts, is captured once in its initial state, and renders to a PNG. A timeline treats it as an image source whose clip declares the duration.
23
+ _Avoid_: Still video, one-frame composition
24
+
25
+ **Declared format**:
26
+ The width, height, and frame rate a composition fragment declares on its root. A value may be absent, in which case it is inherited; a declared value always wins over the project format.
27
+ _Avoid_: Composition metadata, defaults
28
+
29
+ **Resolved format**:
30
+ The width, height, and frame rate an animated composition actually renders with, after declared values and the project format are combined. A static composition resolves width and height only. It is part of the immutable frame environment.
31
+ _Avoid_: Output options, render settings
32
+
21
33
  **Composition metadata**:
22
- The fixed dimensions, frame rate, and duration in frames that define a composition's source contract.
34
+ The resolved dimensions plus the frame rate and duration that define an animated composition's source contract, or the dimensions and `static: true` of a static composition.
23
35
  _Avoid_: Render options, props
24
36
 
25
37
  **Composition descriptor**:
26
- The fixed facts read from an HTML composition before frame production: composition metadata, default props, and the controller declaration. It excludes per-load declarations such as audio clips, transcripts, and animation schedules.
38
+ The fixed facts read from a composition fragment before frame production: declared composition metadata, default props, and the controller declaration. It excludes per-load declarations such as audio clips, transcripts, and animation schedules.
27
39
  _Avoid_: Composition config, parsed document
28
40
 
29
41
  **Composition lifecycle**:
30
- The host-driven sequence that loads an HTML composition, seeks it to frames, and releases it.
42
+ The host-driven sequence that loads a composition fragment, optionally seeks it to frames, and releases it. A static composition is loaded, captured once, and released without seeking.
31
43
  _Avoid_: Render loop, playback lifecycle
32
44
 
33
45
  **Controller lifecycle**:
34
- The `mount → update → dispose` sequence a composition controller uses for per-frame computation.
46
+ The `mount → update → dispose` sequence an animated composition controller uses for per-frame computation. A static composition runs `mount` and `dispose` only.
35
47
  _Avoid_: Composition lifecycle
36
48
 
37
49
  **Composition revision**:
@@ -39,23 +51,27 @@ A specific version of a composition's source and metadata.
39
51
  _Avoid_: Build, release
40
52
 
41
53
  **HTML composition**:
42
- A composition whose source is an HTML document.
43
- _Avoid_: HTML template, page
54
+ A composition whose source is a partial HTML fragment.
55
+ _Avoid_: HTML document, page
56
+
57
+ **Composition fragment**:
58
+ The authoring source of an HTML composition: body content with one top-level `<fy-composition>` root, plus optional top-level props, controller, import maps, and styles. Frameyard generates the document shell.
59
+ _Avoid_: HTML document, page
44
60
 
45
61
  **Transparent composition**:
46
62
  A composition that does not paint a background, so a timeline clip can reveal the layers below it.
47
63
  _Avoid_: Alpha composition, overlay
48
64
 
49
65
  **Composition controller**:
50
- An optional JavaScript controller, either inline in the composition document or in an external module, that performs per-frame computation an HTML composition's declarative HTML and CSS cannot express.
66
+ An optional JavaScript controller, either inline in the composition fragment or in an external module, that performs per-frame computation an HTML composition's declarative HTML and CSS cannot express.
51
67
  _Avoid_: Composition module, plugin
52
68
 
53
69
  **Inline controller**:
54
- A composition controller whose source is embedded in the composition document.
70
+ A composition controller whose source is embedded in the composition fragment.
55
71
  _Avoid_: Embedded script
56
72
 
57
73
  **External controller**:
58
- A composition controller whose source is a separate JavaScript module referenced by the composition document.
74
+ A composition controller whose source is a separate JavaScript module referenced by the composition fragment.
59
75
  _Avoid_: Controller file
60
76
 
61
77
  **Declarative binding**:
@@ -135,13 +151,79 @@ How a clip source's natural frame is fitted into the project frame or an explici
135
151
  _Avoid_: Resize mode, object-fit
136
152
 
137
153
  **Clip transform**:
138
- The static size, position, rotation, and opacity applied to a visual clip. Size is either an explicit `width`/`height` box or a `scale` multiplier.
154
+ The size, position, rotation, and opacity applied to a visual clip. Size is either an explicit `width`/`height` box or a `scale` multiplier. Static transform values and keyframe tracks are separate; a keyed channel cannot also be a static scalar.
139
155
  _Avoid_: Effect, animation
140
156
 
157
+ **Clip keyframes**:
158
+ A per-channel animation on a visual clip's presentation: `x`, `y`, `scale`, `rotation`, or `opacity`. Keys use clip-local integer frames and hold before the first and after the last key.
159
+ _Avoid_: CSS keyframes, composition animation
160
+
161
+ **Keyframe**:
162
+ One `{ frame, value, easing? }` entry in a clip keyframe track. `frame` is clip-local; `easing` applies to the interval after it.
163
+ _Avoid_: Marker, cue
164
+
165
+ **Keyframe track**:
166
+ The ordered keyframes for one presentation channel. Keys are strictly increasing and stay within the clip's `0..duration` range.
167
+ _Avoid_: Animation curve, property lane
168
+
169
+ **Easing**:
170
+ A CSS timing function that shapes the interval between two timeline keyframes. Preview evaluates it exactly; production approximates it in the FFmpeg filter graph.
171
+ _Avoid_: Interpolation, curve
172
+
173
+ **Blend mode**:
174
+ The static CSS `mix-blend-mode` a visual clip applies against the accumulated backdrop of all lower layers and the timeline background. It respects the clip's source alpha and is not animated.
175
+ _Avoid_: Filter, effect, composite mode
176
+
177
+ **Transition**:
178
+ A declared handoff between two clips on one track, written as `{ from, to, kind, easing? }` in the timeline's `transitions` array. `dissolve` is a visual fade on a video track; `crossfade` is an audio gain ramp on an audio track. The clips overlap; an overlap without a declared transition is still an error.
179
+ _Avoid_: Overlap
180
+
181
+ **Crossfade**:
182
+ The audio-track transition kind. It ramps the outgoing audio clip down and the incoming clip up with equal power and changes no pixels.
183
+ _Avoid_: Dissolve, fade
184
+
185
+ **Transition window**:
186
+ The overlap `to.start .. from.end` where both clips are active. During a dissolve the incoming clip composites above the outgoing clip with `opacity × eased progress`, evaluated at absolute timeline frames.
187
+ _Avoid_: Handle, overlap region
188
+
189
+ **Transition role**:
190
+ The side a clip takes in a transition: `out` for the clip being replaced, `in` for the clip replacing it.
191
+ _Avoid_: Transition direction, fade side
192
+
193
+ **Equal-power crossfade**:
194
+ The audio gain curve inside either transition kind: the outgoing clip ramps as `sqrt(1 - t)` and the incoming clip as `sqrt(t)` after easing, keeping perceived loudness steady.
195
+ _Avoid_: Linear fade
196
+
141
197
  **Nested timeline**:
142
198
  A timeline used as a clip source inside another timeline.
143
199
  _Avoid_: Compound composition
144
200
 
201
+ ### Fonts
202
+
203
+ **Font manifest**:
204
+ The versioned `fy.fonts.json` file that records vendored font families, their CSS family names, providers, licenses, and file hashes. It is the source of truth for the generated `@font-face` sheet.
205
+ _Avoid_: Font config, font cache
206
+
207
+ **Font provider**:
208
+ The acquisition source recorded for a font family: `local` for a user-supplied file, `fontsource` for a pinned Fontsource download, or `url` for an explicit HTTPS source.
209
+ _Avoid_: Font catalog, font CDN
210
+
211
+ **Font family entry**:
212
+ One manifest entry keyed by a stable id. It contains the CSS family name, provider metadata, license, optional variable axes, and one or more font files.
213
+ _Avoid_: Font face, font package
214
+
215
+ **Font face**:
216
+ One file entry inside a family: a subset, style, weight or weight range, format, optional unicode range, SHA-256, and byte size.
217
+ _Avoid_: Font variant, font weight
218
+
219
+ **Font subset**:
220
+ A provider-defined slice of a family's glyph coverage, such as `latin` or `latin-ext`. Subsets are recorded per face and may carry a CSS `unicode-range`.
221
+ _Avoid_: Language, charset
222
+
223
+ **Variable font**:
224
+ A font file that carries a continuous range for one or more axes instead of separate static weights. Frameyard records the axes and emits a CSS weight range; the first version installs the `wght` axis only.
225
+ _Avoid_: Dynamic font, multiple weight font
226
+
145
227
  ### Deterministic execution
146
228
 
147
229
  **Frame**:
@@ -152,6 +234,10 @@ _Avoid_: Timestamp, animation tick
152
234
  A clip whose timeline range contains the current timeline frame.
153
235
  _Avoid_: Visible clip
154
236
 
237
+ **Timeline scene**:
238
+ The ordered active clips and their presentation at one timeline frame or over one render range, including transition roles and composite order. It is the timeline-level arrangement, not a composition.
239
+ _Avoid_: Timeline composition, frame snapshot
240
+
155
241
  **Clip capture**:
156
242
  The pixel frames a composition produces for a timeline clip before the timeline compositor combines layers.
157
243
  _Avoid_: Screenshot, render
@@ -225,7 +311,7 @@ _Avoid_: Live audio
225
311
  ### Rendering
226
312
 
227
313
  **Composition host**:
228
- The injected runtime that loads an HTML composition, applies its bindings, and exposes its frame clock to preview and render.
314
+ The injected runtime that loads a composition fragment, applies its bindings, and exposes its frame clock to preview and render.
229
315
  _Avoid_: Preview runtime, render runtime
230
316
 
231
317
  **Timeline host**:
@@ -249,11 +335,11 @@ The preview surface that shows the timeline at the playhead.
249
335
  _Avoid_: Stage, canvas
250
336
 
251
337
  **Check**:
252
- A validation operation that first runs static validation, then loads a timeline or composition and evaluates every frame without capturing or encoding.
338
+ A validation operation that first runs static validation, then loads a timeline or composition and evaluates every frame without capturing or encoding. A static composition has one state, so it is loaded and inspected once.
253
339
  _Avoid_: Test render, dry run
254
340
 
255
341
  **Static composition validation**:
256
- The Node-side validation of a composition document's metadata, bindings, controller declaration, scripts, and asset references before browser frame production.
342
+ The Node-side validation of a composition fragment's metadata, bindings, controller declaration, scripts, and asset references before browser frame production.
257
343
  _Avoid_: Lint, preflight
258
344
 
259
345
  **Static timeline validation**:
@@ -269,9 +355,13 @@ A structured error or warning about a timeline's source, tracks, clips, referenc
269
355
  _Avoid_: Log message, error string
270
356
 
271
357
  **Render**:
272
- The production of one complete video from a timeline or a standalone composition with fixed inputs.
358
+ The production of one complete video from a timeline or an animated standalone composition, or one PNG from a static standalone composition, with fixed inputs.
273
359
  _Avoid_: Recording, export
274
360
 
361
+ **Static composition render**:
362
+ The PNG produced by `render` for a static composition. It has no frame rate, duration, audio, or encoder.
363
+ _Avoid_: Still, frame export
364
+
275
365
  **Render concurrency**:
276
366
  The optional number of independent browser sessions used to seek and capture frames during a composition render; timeline rendering is sequential in 0.6. Frames are still encoded in timeline order, and concurrency must not change visual state.
277
367
  _Avoid_: Parallel composition, out-of-order output
@@ -281,13 +371,53 @@ The finished audiovisual output of a successful render.
281
371
  _Avoid_: Composition, project
282
372
 
283
373
  **Still**:
284
- One or more requested frames captured as PNGs through the production rendering path for a timeline or composition. A single invocation captures at most 32 inspection frames; this is not image-sequence output.
374
+ One or more requested frames captured as PNGs through the production rendering path for a timeline or composition. A static composition exposes exactly frame 0. A single invocation captures at most 32 inspection frames; this is not image-sequence output.
285
375
  _Avoid_: Frame export, image sequence
286
376
 
287
377
  **Reproducibility**:
288
378
  The guarantee that, for the same timeline or composition revision, props, assets, and frame under a pinned runtime/browser environment, the visual state is identical. It does not imply identical encoded bytes, even on the same machine; encoded output depends on the encoder and its runtime.
289
379
  _Avoid_: Bit-for-bit encoding equality
290
380
 
381
+ ### Resource policy
382
+
383
+ **Resource policy**:
384
+ A workspace-local `fy.policy.json` declaration of which CLI operations are allowed and which resource limits apply. It is discovered from the working directory upward, independent of `fy.config.js`, and can only tighten its tier.
385
+ _Avoid_: Project config, agent runtime
386
+
387
+ **Policy tier**:
388
+ One of `light`, `authoring`, or `full`. A tier fixes the allowed heavy operations, the still-frame cap (1, 4, or the library cap of 32), and overwrite permission. `FRAMEYARD_POLICY` replaces the policy file for a human override.
389
+ _Avoid_: Permission mode, sandbox
390
+
391
+ **Resource profile**:
392
+ A named set of runtime caps and priority settings for a shared machine. `shared-cpu` caps timeline workers at two, composition concurrency at one, and lowers process priority.
393
+ _Avoid_: Encoder preset, render mode
394
+
395
+ **Lint**:
396
+ A Node-only validation pass that resolves a composition or timeline, projects every timeline scene and keyframe channel, scans controller source for wall-clock and randomness calls, and probes media metadata without launching Chromium. Lint is not a substitute for `check`.
397
+ _Avoid_: Static check, fast render
398
+
399
+ **Dry run**:
400
+ A heavy command mode that validates inputs and returns a plan, handoff command, prerequisites, and blockers without writing outputs, loading a model, launching Chromium, or encoding.
401
+ _Avoid_: Preview, partial render
402
+
403
+ ### Inspection and templates
404
+
405
+ **Project context**:
406
+ A compact, read-only digest of a project's format, composition metadata, prop types, timeline clips, assets, installed fonts, diagnostics, active policy, and next commands.
407
+ _Avoid_: Project dump, repo summary
408
+
409
+ **Image inspection**:
410
+ Text facts extracted from a decoded still: dimensions, alpha coverage, blank detection, background and dominant colors, content bounding box, luminance grid, and edge density.
411
+ _Avoid_: Visual review, AI vision
412
+
413
+ **Image comparison**:
414
+ A pixel and region diff between two decoded images. It reports differing pixels, channel deltas, a bounding box, and a 3×3 region breakdown.
415
+ _Avoid_: Pixel hash, visual diff
416
+
417
+ **Project template**:
418
+ A known-good composition or timeline entry that `add` writes while printing the explicit `fy.config.js` registration snippet.
419
+ _Avoid_: Generator, project scaffold
420
+
291
421
  ### Agent operation
292
422
 
293
423
  **Agent-assisted authoring and operation**:
@@ -309,9 +439,13 @@ A versioned, project-local record of generated assets, their content hashes, and
309
439
  _Avoid_: Render cache
310
440
 
311
441
  **Speech synthesis** (TTS):
312
- The local production of a spoken audio asset from text using a named speaker.
442
+ The local production of a spoken audio asset from text using a named speaker or numeric speaker id.
313
443
  _Avoid_: Voice generation, voice cloning, voice design
314
444
 
445
+ **Speech engine**:
446
+ A local executable that turns text into a speech asset. Frameyard supports qwentts.cpp CustomVoice and sherpa-onnx Supertonic 3.
447
+ _Avoid_: TTS provider, cloud voice
448
+
315
449
  **Transcription**:
316
450
  The local production of timed text from source audio or video.
317
451
  _Avoid_: Subtitle rendering