frameyard 0.3.0 → 0.5.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.
- package/CHANGELOG.md +117 -0
- package/GLOSSARY.md +46 -2
- package/README.md +174 -106
- package/dist/bindings.d.ts +42 -0
- package/dist/bindings.d.ts.map +1 -0
- package/dist/bindings.js +144 -0
- package/dist/bindings.js.map +1 -0
- package/dist/cli.js +32 -21
- package/dist/cli.js.map +1 -1
- package/dist/composition/adapter.d.ts +30 -0
- package/dist/composition/adapter.d.ts.map +1 -0
- package/dist/composition/adapter.js +2 -0
- package/dist/composition/adapter.js.map +1 -0
- package/dist/composition/animation.d.ts +19 -0
- package/dist/composition/animation.d.ts.map +1 -0
- package/dist/composition/animation.js +55 -0
- package/dist/composition/animation.js.map +1 -0
- package/dist/composition/attributes.d.ts +7 -0
- package/dist/composition/attributes.d.ts.map +1 -0
- package/dist/composition/attributes.js +24 -0
- package/dist/composition/attributes.js.map +1 -0
- package/dist/composition/descriptor.d.ts +30 -0
- package/dist/composition/descriptor.d.ts.map +1 -0
- package/dist/composition/descriptor.js +188 -0
- package/dist/composition/descriptor.js.map +1 -0
- package/dist/composition/diagnostics.d.ts +12 -0
- package/dist/composition/diagnostics.d.ts.map +1 -0
- package/dist/composition/diagnostics.js +38 -0
- package/dist/composition/diagnostics.js.map +1 -0
- package/dist/composition/index.d.ts +12 -0
- package/dist/composition/index.d.ts.map +1 -0
- package/dist/composition/index.js +7 -0
- package/dist/composition/index.js.map +1 -0
- package/dist/composition/media.d.ts +27 -0
- package/dist/composition/media.d.ts.map +1 -0
- package/dist/composition/media.js +162 -0
- package/dist/composition/media.js.map +1 -0
- package/dist/composition/paths.d.ts +5 -0
- package/dist/composition/paths.d.ts.map +1 -0
- package/dist/composition/paths.js +16 -0
- package/dist/composition/paths.js.map +1 -0
- package/dist/composition/transcripts.d.ts +19 -0
- package/dist/composition/transcripts.d.ts.map +1 -0
- package/dist/composition/transcripts.js +57 -0
- package/dist/composition/transcripts.js.map +1 -0
- package/dist/composition/walk.d.ts +10 -0
- package/dist/composition/walk.d.ts.map +1 -0
- package/dist/composition/walk.js +32 -0
- package/dist/composition/walk.js.map +1 -0
- package/dist/contracts.d.ts +75 -47
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +14 -159
- package/dist/contracts.js.map +1 -1
- package/dist/errors.d.ts +15 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +8 -0
- package/dist/errors.js.map +1 -1
- package/dist/html/document-adapter.d.ts +5 -0
- package/dist/html/document-adapter.d.ts.map +1 -0
- package/dist/html/document-adapter.js +17 -0
- package/dist/html/document-adapter.js.map +1 -0
- package/dist/html/index.d.ts +5 -0
- package/dist/html/index.d.ts.map +1 -0
- package/dist/html/index.js +3 -0
- package/dist/html/index.js.map +1 -0
- package/dist/html/parse.d.ts +26 -0
- package/dist/html/parse.d.ts.map +1 -0
- package/dist/html/parse.js +80 -0
- package/dist/html/parse.js.map +1 -0
- package/dist/html/validate.d.ts +14 -0
- package/dist/html/validate.d.ts.map +1 -0
- package/dist/html/validate.js +648 -0
- package/dist/html/validate.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/preview/main.d.ts +10 -3
- package/dist/preview/main.d.ts.map +1 -1
- package/dist/preview/main.js +189 -89
- package/dist/preview/main.js.map +1 -1
- package/dist/project.d.ts +2 -0
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +4 -0
- package/dist/project.js.map +1 -1
- package/dist/renderer/browser.d.ts +11 -1
- package/dist/renderer/browser.d.ts.map +1 -1
- package/dist/renderer/browser.js +93 -14
- package/dist/renderer/browser.js.map +1 -1
- package/dist/renderer/index.d.ts +25 -5
- package/dist/renderer/index.d.ts.map +1 -1
- package/dist/renderer/index.js +82 -16
- package/dist/renderer/index.js.map +1 -1
- package/dist/runtime/bindings.d.ts +11 -0
- package/dist/runtime/bindings.d.ts.map +1 -0
- package/dist/runtime/bindings.js +164 -0
- package/dist/runtime/bindings.js.map +1 -0
- package/dist/runtime/diagnostics.d.ts +4 -0
- package/dist/runtime/diagnostics.d.ts.map +1 -0
- package/dist/runtime/diagnostics.js +107 -0
- package/dist/runtime/diagnostics.js.map +1 -0
- package/dist/runtime/document-adapter.d.ts +4 -0
- package/dist/runtime/document-adapter.d.ts.map +1 -0
- package/dist/runtime/document-adapter.js +19 -0
- package/dist/runtime/document-adapter.js.map +1 -0
- package/dist/runtime/host.d.ts +14 -0
- package/dist/runtime/host.d.ts.map +1 -0
- package/dist/runtime/host.js +596 -0
- package/dist/runtime/host.js.map +1 -0
- package/dist/runtime/index.d.ts +0 -29
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +0 -360
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/media.d.ts.map +1 -1
- package/dist/runtime/media.js +20 -51
- package/dist/runtime/media.js.map +1 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +177 -84
- package/dist/scaffold.js.map +1 -1
- package/dist/server/composition-html.d.ts +20 -0
- package/dist/server/composition-html.d.ts.map +1 -0
- package/dist/server/composition-html.js +45 -0
- package/dist/server/composition-html.js.map +1 -0
- package/dist/server/preview-html.d.ts.map +1 -1
- package/dist/server/preview-html.js +19 -14
- package/dist/server/preview-html.js.map +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +94 -6
- package/dist/server/server.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/adr/0001-keep-composition-authoring-web-native.md +2 -0
- package/docs/adr/0006-seek-embedded-media-from-the-frame-clock.md +7 -1
- package/docs/adr/0007-make-html-documents-the-composition-unit.md +31 -0
- package/docs/adr/0008-share-the-composition-document-grammar.md +18 -0
- package/docs/adr/0009-composite-production-frames-in-software.md +37 -0
- package/docs/migration-0.4.md +279 -0
- package/docs/v0.1-design.md +78 -56
- package/examples/demo/README.md +8 -4
- package/examples/demo/compositions/demo.controller.js +120 -0
- package/examples/demo/compositions/demo.html +253 -0
- package/examples/demo/video.config.js +1 -1
- package/examples/video/README.md +61 -0
- package/examples/video/assets/clip.mp4 +0 -0
- package/examples/video/assets/clip.webm +0 -0
- package/examples/video/compositions/video-mp4.html +362 -0
- package/examples/video/compositions/video.html +362 -0
- package/examples/video/props/video.json +8 -0
- package/examples/video/scripts/make-clip.sh +109 -0
- package/examples/video/video.config.js +6 -0
- package/examples/webgl/README.md +30 -0
- package/examples/webgl/compositions/webgl.controller.js +200 -0
- package/examples/webgl/compositions/webgl.html +215 -0
- package/examples/webgl/props/webgl.json +6 -0
- package/examples/webgl/video.config.js +5 -0
- package/package.json +7 -3
- package/skills/frameyard/README.md +4 -0
- package/skills/frameyard/SKILL.md +40 -12
- package/skills/frameyard/references/audio-assets.md +25 -14
- package/skills/frameyard/references/bindings.md +73 -0
- package/skills/frameyard/references/common-mistakes.md +59 -0
- package/skills/frameyard/references/controllers.md +126 -0
- package/skills/frameyard/references/deterministic-authoring.md +143 -52
- package/skills/frameyard/references/diagnostics.md +90 -0
- package/skills/frameyard/references/failure-recovery.md +37 -14
- package/skills/frameyard/references/json-results.md +43 -9
- package/skills/frameyard/references/subtitles-karaoke.md +10 -18
- package/examples/demo/compositions/demo.js +0 -206
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Bindings
|
|
2
|
+
|
|
3
|
+
Bindings map immutable props into the composition DOM once per load. They are explicit HTML attributes; there is no template expression language.
|
|
4
|
+
|
|
5
|
+
## Attributes
|
|
6
|
+
|
|
7
|
+
| Attribute | Effect |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `data-fy-text="path"` | Sets `textContent`. |
|
|
10
|
+
| `data-fy-attr-<name>="path"` | Sets an attribute. |
|
|
11
|
+
| `data-fy-class-<name>="path"` | Toggles one class. |
|
|
12
|
+
| `data-fy-if="path"` | Removes the element when false. |
|
|
13
|
+
| `data-fy-repeat="path"` on `<template>` | Clones the template content for each array item. |
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<h1 data-fy-text="title"></h1>
|
|
17
|
+
<img data-fy-attr-src="image" data-fy-attr-alt="alt">
|
|
18
|
+
<div data-fy-class-active="isActive"></div>
|
|
19
|
+
<p data-fy-if="showSubtitle" data-fy-text="subtitle"></p>
|
|
20
|
+
|
|
21
|
+
<template data-fy-repeat="items">
|
|
22
|
+
<span data-fy-text="item.label" data-fy-attr-data-index="index"></span>
|
|
23
|
+
</template>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Paths
|
|
27
|
+
|
|
28
|
+
- Dot-separated: `title`, `user.name`, `items.0.label`.
|
|
29
|
+
- Numeric segments index arrays.
|
|
30
|
+
- Only own properties are read. `__proto__`, `constructor`, and `prototype` are rejected.
|
|
31
|
+
- Inside a repeat clone, `item` and `index` shadow props.
|
|
32
|
+
- Missing paths are warnings. Text becomes empty, attributes/classes are removed, and `if` is false. A missing or non-array `repeat` path is an error.
|
|
33
|
+
|
|
34
|
+
## Value coercion
|
|
35
|
+
|
|
36
|
+
| Binding | Rule |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `text` | Strings, numbers, and booleans are stringified; null/undefined become empty; objects/arrays warn and become empty. |
|
|
39
|
+
| `attr` | `true` writes an empty attribute; `false`/null/undefined remove it; strings/numbers are stringified; objects/arrays warn and remove. |
|
|
40
|
+
| `class` | JavaScript truthiness; objects/arrays warn and remove. |
|
|
41
|
+
| `if` | JavaScript truthiness. Empty arrays are truthy. |
|
|
42
|
+
| `repeat` | Must be an array; otherwise an error. |
|
|
43
|
+
|
|
44
|
+
`data-fy-attr-on*` is rejected. A bound `href`/`src` that resolves to a `javascript:` URL is rejected.
|
|
45
|
+
|
|
46
|
+
## Repeat scope
|
|
47
|
+
|
|
48
|
+
```html
|
|
49
|
+
<ul>
|
|
50
|
+
<template data-fy-repeat="items">
|
|
51
|
+
<li data-fy-text="item.label"></li>
|
|
52
|
+
</template>
|
|
53
|
+
</ul>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- The `<template>` is replaced by the clones.
|
|
57
|
+
- `--fy-index` is set on each top-level clone for CSS stagger.
|
|
58
|
+
- Nested repeats are rejected. Use a controller for nested data.
|
|
59
|
+
- A repeat template cannot also use `data-fy-if` or `data-fy-text`.
|
|
60
|
+
|
|
61
|
+
## CSS variables
|
|
62
|
+
|
|
63
|
+
Primitive top-level props are also set as `--fy-prop-<name>` on the composition root:
|
|
64
|
+
|
|
65
|
+
```html
|
|
66
|
+
<div style="background: var(--fy-prop-accent)"></div>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Booleans are `1`/`0`. Null, undefined, objects, and arrays are omitted. Bindings are applied before the controller mounts; controller-created DOM is not re-bound.
|
|
70
|
+
|
|
71
|
+
## Ordering
|
|
72
|
+
|
|
73
|
+
Bindings apply top-down: repeat, then `if`, then text/attribute/class. A removed element does not process its children. `data-fy-if` and `data-fy-text` on the composition root are errors.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Common mistakes
|
|
2
|
+
|
|
3
|
+
## Using the old authoring model
|
|
4
|
+
|
|
5
|
+
- **JavaScript composition modules / `defineComposition`** were removed in 0.4. A composition is a `.html` document listed in `video.config.js`.
|
|
6
|
+
- **`data-frameyard-*`** media attributes were removed in 0.4. Use `data-fy-*`.
|
|
7
|
+
|
|
8
|
+
## Document structure
|
|
9
|
+
|
|
10
|
+
- Missing `<!doctype html>` or missing/multiple `<fy-composition>` roots.
|
|
11
|
+
- Missing `width`, `height`, `fps`, or `duration-in-frames` on the root.
|
|
12
|
+
- Missing the `data-fy="props"` block when the composition relies on default props.
|
|
13
|
+
- Visual content outside `<fy-composition>`; it is not captured.
|
|
14
|
+
- Using `<base>`, refresh/CSP meta tags, `<iframe>`, `<object>`, or `<embed>`.
|
|
15
|
+
|
|
16
|
+
## Bindings
|
|
17
|
+
|
|
18
|
+
- Unknown `data-fy-*` attribute, usually a typo such as `data-fy-txt`.
|
|
19
|
+
- A binding path that does not exist in the resolved props. Check the `BINDING_PATH_MISSING` warning and its suggestion.
|
|
20
|
+
- Using `data-fy-repeat` on a normal element instead of `<template>`.
|
|
21
|
+
- Nesting `data-fy-repeat`; use a controller for nested data.
|
|
22
|
+
- Putting `data-fy-if` or `data-fy-text` on a repeat template.
|
|
23
|
+
- Putting `data-fy-text` on an `<img>` or another void element.
|
|
24
|
+
- Expecting bindings to update every frame or to re-bind controller-created DOM.
|
|
25
|
+
|
|
26
|
+
## Timing
|
|
27
|
+
|
|
28
|
+
- Writing seconds where frames are required, or frames where seconds are required.
|
|
29
|
+
- `data-fy-start` and `data-fy-anim-start`/`duration` are frames.
|
|
30
|
+
- `data-fy-trim`/`duration` are seconds.
|
|
31
|
+
- Using CSS `animation-delay`/`duration` in seconds and expecting the declarative scheduler to ignore its own attributes. `data-fy-anim-*` overrides the shorthand's delay/duration.
|
|
32
|
+
- Adding `data-fy-anim-*` without a CSS animation; this produces `ANIMATION_NOT_FOUND`.
|
|
33
|
+
- Using multiple animations on an element with one declarative schedule.
|
|
34
|
+
|
|
35
|
+
## Controllers
|
|
36
|
+
|
|
37
|
+
- Declaring both inline source and `src` on the same controller.
|
|
38
|
+
- Declaring more than one controller.
|
|
39
|
+
- Making `update()` async; do asynchronous work in `mount()`.
|
|
40
|
+
- Leaving module-scope state behind in `dispose()`.
|
|
41
|
+
- Writing a literal `</script>` inside inline controller source; escape it.
|
|
42
|
+
- Importing libraries from executable `<script>` tags; import them from the controller instead.
|
|
43
|
+
- Using `Math.random()`, `Date.now()`, `performance.now()`, timers, independent `requestAnimationFrame` loops, or CSS transitions.
|
|
44
|
+
|
|
45
|
+
## Media and transcripts
|
|
46
|
+
|
|
47
|
+
- Using remote, live, or unseekable media sources.
|
|
48
|
+
- Using `<source>` or `<track>` children; use a local `src` and `<fy-transcript>`.
|
|
49
|
+
- Adding or changing media in `update()`; prepare it in the document or in `mount()`.
|
|
50
|
+
- Calling `play()`, `pause()`, or setting `currentTime` on a video.
|
|
51
|
+
- Assuming media/transcript `src` is document-relative. It is project-root-relative.
|
|
52
|
+
- Assuming the controller `src` is project-root-relative. It is document-relative.
|
|
53
|
+
|
|
54
|
+
## Validation
|
|
55
|
+
|
|
56
|
+
- Replacing `check` with a single `still`.
|
|
57
|
+
- Ignoring warnings or `diagnostics` in `--json` output.
|
|
58
|
+
- Claiming success after a nonzero exit, a missing still, or skipped visual inspection.
|
|
59
|
+
- Using `--overwrite` or `--force` without permission.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Controllers
|
|
2
|
+
|
|
3
|
+
A composition controller is optional JavaScript for what HTML and CSS cannot express: Canvas 2D, WebGL, karaoke, complex computed motion, or data-driven DOM. It is the only executable code in a composition.
|
|
4
|
+
|
|
5
|
+
## Inline or external
|
|
6
|
+
|
|
7
|
+
Inline for a small, single-use controller:
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<script type="text/fy-controller">
|
|
11
|
+
export default {
|
|
12
|
+
mount({ root, props }) {
|
|
13
|
+
// create canvases, query elements, load data
|
|
14
|
+
},
|
|
15
|
+
update({ frame, props, root }) {
|
|
16
|
+
// synchronous and seek-safe
|
|
17
|
+
},
|
|
18
|
+
dispose() {
|
|
19
|
+
// release module-scope state and listeners
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
</script>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
External for reuse, tests, or a large controller:
|
|
26
|
+
|
|
27
|
+
```html
|
|
28
|
+
<script type="text/fy-controller" src="./hello.controller.js"></script>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
export default {
|
|
33
|
+
mount({ root, props }) {},
|
|
34
|
+
update({ frame, props, root }) {},
|
|
35
|
+
dispose() {},
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Exactly one controller is allowed. Inline source is served through a virtual module URL, so relative imports and `import.meta.url` work like an external module. A literal `</script>` inside inline source must be escaped.
|
|
40
|
+
|
|
41
|
+
## Lifecycle
|
|
42
|
+
|
|
43
|
+
- `mount({ root, props })` runs once per load and may be asynchronous. Use it to query the root, create canvases, and start loading local data.
|
|
44
|
+
- `update({ frame, props, root })` runs on every seek. It must be synchronous and seek-safe: derive the frame from `frame` and `props` only.
|
|
45
|
+
- `dispose()` releases resources and resets module-scope state. The host calls it before remounting or disposing the document.
|
|
46
|
+
- `validateProps(props)` is optional and synchronous. Return an array of human-readable errors, or `void`.
|
|
47
|
+
- A validation-only controller may export just `validateProps`.
|
|
48
|
+
|
|
49
|
+
Keep mutable state in module scope, never on `this`. The controller module is imported once per document; `load` remounts it without re-importing, so `dispose` must reset module state.
|
|
50
|
+
|
|
51
|
+
## Props validation
|
|
52
|
+
|
|
53
|
+
```html
|
|
54
|
+
<script type="text/fy-controller">
|
|
55
|
+
export default {
|
|
56
|
+
validateProps(props) {
|
|
57
|
+
const errors = [];
|
|
58
|
+
if (typeof props.title !== "string") errors.push("title must be a string");
|
|
59
|
+
return errors;
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
</script>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Validation runs before the old mount is disposed. Invalid props reject `load` with `PROPS_INVALID` and keep the current composition mounted.
|
|
66
|
+
|
|
67
|
+
## Canvas and WebGL
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
let canvas;
|
|
71
|
+
let context;
|
|
72
|
+
|
|
73
|
+
export default {
|
|
74
|
+
mount({ root }) {
|
|
75
|
+
canvas = root.querySelector("#field");
|
|
76
|
+
context = canvas.getContext("2d");
|
|
77
|
+
},
|
|
78
|
+
update({ frame }) {
|
|
79
|
+
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
80
|
+
context.fillStyle = frame % 2 === 0 ? "#101820" : "#203040";
|
|
81
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
82
|
+
},
|
|
83
|
+
dispose() {
|
|
84
|
+
canvas = undefined;
|
|
85
|
+
context = undefined;
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Redraw WebGL from the current frame; do not depend on the previous drawing buffer. Production pins SwiftShader.
|
|
91
|
+
|
|
92
|
+
## Randomness and clocks
|
|
93
|
+
|
|
94
|
+
Import `random` from `frameyard/runtime`:
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
import { random } from "frameyard/runtime";
|
|
98
|
+
|
|
99
|
+
const x = random("star:17:x") * 1920;
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Never use `Math.random()`, `Date.now()`, `performance.now()`, timers, independent `requestAnimationFrame` loops, or CSS transitions as animation inputs. The host diagnoses common cases in `mount` and `update`.
|
|
103
|
+
|
|
104
|
+
## Media
|
|
105
|
+
|
|
106
|
+
Prefer declarative `<video>`/`<audio>` elements with `data-fy-*` attributes. The host pauses and seeks them; do not call `play()`, `pause()`, or set `currentTime` in `update()`. Media created by the controller is allowed in `mount`; media added or changed in `update` cannot be prepared without network access.
|
|
107
|
+
|
|
108
|
+
Media and transcript `src` values resolve from the project root. The controller `src` resolves relative to the composition document.
|
|
109
|
+
|
|
110
|
+
## Karaoke
|
|
111
|
+
|
|
112
|
+
Read the declared transcript JSON, convert source milliseconds to frames, and render the active segment or word from `frame`:
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
const FPS = 30;
|
|
116
|
+
const clipStartFrame = 30;
|
|
117
|
+
const trimStart = 0.25;
|
|
118
|
+
|
|
119
|
+
function wordProgress(word, frame) {
|
|
120
|
+
const start = clipStartFrame + (word.startMs / 1000 - trimStart) * FPS;
|
|
121
|
+
const end = clipStartFrame + (word.endMs / 1000 - trimStart) * FPS;
|
|
122
|
+
return Math.max(0, Math.min(1, (frame - start) / Math.max(1, end - start)));
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Never interpolate segment timings to fake word timings.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Deterministic authoring
|
|
1
|
+
# Deterministic HTML authoring
|
|
2
2
|
|
|
3
|
-
Frameyard compositions are
|
|
3
|
+
Frameyard compositions are full HTML documents. The preview server injects the framework import map and composition host; do not add a bundling step. Optional controllers are native JavaScript modules that import bare `frameyard/runtime`.
|
|
4
4
|
|
|
5
5
|
## Project configuration
|
|
6
6
|
|
|
@@ -9,50 +9,125 @@ Frameyard compositions are ordinary native JavaScript ES modules. The preview se
|
|
|
9
9
|
```js
|
|
10
10
|
export default {
|
|
11
11
|
compositions: {
|
|
12
|
-
hello: "./compositions/hello.
|
|
12
|
+
hello: "./compositions/hello.html",
|
|
13
13
|
},
|
|
14
14
|
};
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
There is no directory scanning. A new composition
|
|
17
|
+
There is no directory scanning. A new composition document is invisible until it is listed. Entries must be `.html` files; JavaScript composition modules are rejected.
|
|
18
18
|
|
|
19
19
|
## Composition shape
|
|
20
20
|
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
```html
|
|
22
|
+
<!doctype html>
|
|
23
|
+
<html>
|
|
24
|
+
<head>
|
|
25
|
+
<meta charset="utf-8">
|
|
26
|
+
<script type="application/json" data-fy="props">
|
|
27
|
+
{ "title": "Hello", "accent": "#b8f36b" }
|
|
28
|
+
</script>
|
|
29
|
+
<style>
|
|
30
|
+
@keyframes fade-up {
|
|
31
|
+
from { opacity: 0; transform: translateY(24px); }
|
|
32
|
+
to { opacity: 1; transform: translateY(0); }
|
|
33
|
+
}
|
|
34
|
+
#title { animation: fade-up 1s ease-out both; }
|
|
35
|
+
</style>
|
|
36
|
+
</head>
|
|
37
|
+
<body>
|
|
38
|
+
<fy-composition width="1920" height="1080" fps="30" duration-in-frames="900">
|
|
39
|
+
<h1 id="title"
|
|
40
|
+
data-fy-text="title"
|
|
41
|
+
data-fy-anim-start="0"
|
|
42
|
+
data-fy-anim-duration="30"></h1>
|
|
43
|
+
<div style="width: calc(var(--fy-progress) * 100%)"></div>
|
|
44
|
+
</fy-composition>
|
|
45
|
+
</body>
|
|
46
|
+
</html>
|
|
47
|
+
```
|
|
24
48
|
|
|
25
|
-
|
|
49
|
+
- `width`, `height`, `fps`, and `duration-in-frames` are fixed on the root.
|
|
50
|
+
- The `data-fy="props"` block is a JSON object. External props merge shallowly over it and are deep-frozen.
|
|
51
|
+
- There is exactly one `<fy-composition>` root, inside `<body>`.
|
|
52
|
+
- A doctype is required. Unsupported document features such as `<base>`, refresh/CSP meta tags, `<iframe>`, `<object>`, and `<embed>` are rejected.
|
|
26
53
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
54
|
+
## Bindings
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<h1 data-fy-text="title"></h1>
|
|
58
|
+
<img data-fy-attr-src="image" data-fy-attr-alt="alt">
|
|
59
|
+
<div data-fy-class-active="isActive"></div>
|
|
60
|
+
<p data-fy-if="showSubtitle" data-fy-text="subtitle"></p>
|
|
61
|
+
<template data-fy-repeat="items">
|
|
62
|
+
<span data-fy-text="item.label" data-fy-attr-data-index="index"></span>
|
|
63
|
+
</template>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- Paths are dot-separated (`user.name`, `items.0.label`); numeric segments index arrays.
|
|
67
|
+
- `data-fy-text` sets `textContent`.
|
|
68
|
+
- `data-fy-attr-<name>` sets an attribute; `true` writes an empty attribute, `false`/`null`/`undefined` remove it.
|
|
69
|
+
- `data-fy-class-<name>` toggles one class.
|
|
70
|
+
- `data-fy-if` removes the element when the value is false.
|
|
71
|
+
- `data-fy-repeat` must be on a `<template>` and must reference an array. Each clone gets `item`, `index`, and `--fy-index`. Nested repeats are rejected; use a controller.
|
|
72
|
+
- Bindings apply once per load, before the controller mounts. Controller-created DOM is not re-bound.
|
|
73
|
+
- Missing paths warn and resolve to empty text, a removed attribute/class, or false for `if`.
|
|
74
|
+
- Primitive top-level props are also exposed as `--fy-prop-<name>` CSS custom properties. Booleans are `1`/`0`; objects and arrays are omitted.
|
|
75
|
+
|
|
76
|
+
## Animation
|
|
77
|
+
|
|
78
|
+
CSS `@keyframes` and the Web Animations API are paused and seeked by the runtime inside the composition root.
|
|
79
|
+
|
|
80
|
+
```css
|
|
81
|
+
@keyframes fade-up {
|
|
82
|
+
from { opacity: 0; transform: translateY(24px); }
|
|
83
|
+
to { opacity: 1; transform: translateY(0); }
|
|
84
|
+
}
|
|
85
|
+
#title { animation: fade-up 1s ease-out both; }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```html
|
|
89
|
+
<h1 data-fy-anim-start="30" data-fy-anim-duration="60" data-fy-anim-easing="ease-out"></h1>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- `data-fy-anim-start` is a non-negative frame count. `data-fy-anim-duration` is a positive frame count. `data-fy-anim-easing` is a CSS easing.
|
|
93
|
+
- One declarative animation per element. The CSS `animation` shorthand supplies keyframes, fill mode, and iteration count; the attributes override delay/duration/easing.
|
|
94
|
+
- The root exposes `--fy-frame`, `--fy-time`, `--fy-progress`, `--fy-fps`, `--fy-duration`, and `--fy-duration-frames`. Use them in `calc()` for simple computed motion and progress bars.
|
|
95
|
+
- Do not use CSS transitions. They are diagnosed as non-deterministic.
|
|
96
|
+
|
|
97
|
+
## Controllers
|
|
98
|
+
|
|
99
|
+
An optional controller handles Canvas 2D, WebGL, karaoke, and per-frame computation CSS cannot express.
|
|
100
|
+
|
|
101
|
+
Inline for a small, single-use controller:
|
|
102
|
+
|
|
103
|
+
```html
|
|
104
|
+
<script type="text/fy-controller">
|
|
105
|
+
export default {
|
|
106
|
+
mount({ root, props }) {
|
|
107
|
+
// query elements, create canvases, load data
|
|
40
108
|
},
|
|
41
|
-
update({ frame, props }) {
|
|
42
|
-
|
|
43
|
-
title.style.transform = `translateX(${random("title:" + frame) * 40}px)`;
|
|
109
|
+
update({ frame, props, root }) {
|
|
110
|
+
// synchronous and seek-safe
|
|
44
111
|
},
|
|
45
112
|
dispose() {
|
|
46
|
-
|
|
113
|
+
// release module-scope state and listeners
|
|
47
114
|
},
|
|
48
|
-
}
|
|
115
|
+
};
|
|
116
|
+
</script>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
External for a reusable or complex controller:
|
|
120
|
+
|
|
121
|
+
```html
|
|
122
|
+
<script type="text/fy-controller" src="./hello.controller.js"></script>
|
|
49
123
|
```
|
|
50
124
|
|
|
51
|
-
- `mount
|
|
52
|
-
-
|
|
53
|
-
- `
|
|
54
|
-
-
|
|
55
|
-
-
|
|
125
|
+
- Exactly one controller is allowed. It may export `mount`, `update`, `dispose`, and `validateProps`; at least one is required. `mount` may be async; `update` and `validateProps` are synchronous.
|
|
126
|
+
- A validation-only controller may export just `validateProps(props)`.
|
|
127
|
+
- Inline source is served through a virtual module URL, so relative imports and `import.meta.url` work like an external module. A literal `</script>` inside inline source must be escaped.
|
|
128
|
+
- The controller is the only executable code. Other executable `<script>` elements and `on*` handlers are rejected.
|
|
129
|
+
- Controller `src` resolves relative to the composition document. Media and transcript `src` values resolve from the project root.
|
|
130
|
+
- Keep mutable state in module scope, never on `this`. Reset it in `dispose()`.
|
|
56
131
|
|
|
57
132
|
## Forbidden animation inputs
|
|
58
133
|
|
|
@@ -65,19 +140,17 @@ Do not use any of these as animation state:
|
|
|
65
140
|
- CSS transitions
|
|
66
141
|
- state left from a previously visited frame
|
|
67
142
|
|
|
68
|
-
Use `random("stable:key")` for deterministic randomness. The value depends only on the key, not call order.
|
|
143
|
+
Use `random("stable:key")` from `frameyard/runtime` for deterministic randomness. The value depends only on the key, not call order.
|
|
69
144
|
|
|
70
145
|
```js
|
|
71
146
|
import { random } from "frameyard/runtime";
|
|
72
147
|
|
|
73
|
-
const x = random("star:17:x") *
|
|
148
|
+
const x = random("star:17:x") * 1920;
|
|
74
149
|
```
|
|
75
150
|
|
|
76
151
|
## Canvas and WebGL
|
|
77
152
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
Canvas 2D and WebGL must be redrawn from the current frame:
|
|
153
|
+
Redraw Canvas 2D and WebGL from the current frame in the controller's `update()`:
|
|
81
154
|
|
|
82
155
|
```js
|
|
83
156
|
update({ frame }) {
|
|
@@ -92,34 +165,52 @@ update({ frame }) {
|
|
|
92
165
|
|
|
93
166
|
Do not preserve and accumulate WebGL drawing across frames unless that accumulation is itself deterministic. Production sessions use software WebGL so output does not depend on the host GPU.
|
|
94
167
|
|
|
95
|
-
## Embedded video
|
|
168
|
+
## Embedded video and audio
|
|
96
169
|
|
|
97
|
-
|
|
170
|
+
Declare local media in the composition document:
|
|
98
171
|
|
|
99
172
|
```html
|
|
100
173
|
<video
|
|
101
174
|
src="assets/clip.webm"
|
|
102
|
-
data-
|
|
103
|
-
data-
|
|
104
|
-
data-
|
|
105
|
-
data-
|
|
175
|
+
data-fy-start="30"
|
|
176
|
+
data-fy-trim="0.5"
|
|
177
|
+
data-fy-duration="4"
|
|
178
|
+
data-fy-volume="0.8"
|
|
106
179
|
></video>
|
|
180
|
+
|
|
181
|
+
<audio
|
|
182
|
+
src="assets/voice.wav"
|
|
183
|
+
data-fy-start="30"
|
|
184
|
+
data-fy-trim="0.25"
|
|
185
|
+
data-fy-duration="4.2"
|
|
186
|
+
data-fy-volume="0.8"
|
|
187
|
+
></audio>
|
|
107
188
|
```
|
|
108
189
|
|
|
109
|
-
- `data-
|
|
110
|
-
- `data-
|
|
111
|
-
- `data-
|
|
112
|
-
- `data-
|
|
190
|
+
- `data-fy-start` is the composition frame where the source starts. Default `0`.
|
|
191
|
+
- `data-fy-trim` is seconds trimmed from the source start. Default `0`.
|
|
192
|
+
- `data-fy-duration` optionally limits how many source seconds are used.
|
|
193
|
+
- `data-fy-volume` sets volume from `0` to `1`. Default `1`.
|
|
194
|
+
|
|
195
|
+
Do not call `play()`, `pause()`, or set `currentTime` in a controller. Media must be in the light DOM; shadow roots are not scanned. Sources must be local project-relative files; live streams, remote URLs, `<source>` children, and `<track>` children are not supported. The runtime loads the complete file during the loading phase and seeks video after each `update()`.
|
|
196
|
+
|
|
197
|
+
An unmuted video's audio is extracted and mixed into the output timeline. Set `muted` or `data-fy-audio="off"` for a silent visual asset.
|
|
113
198
|
|
|
114
|
-
|
|
199
|
+
## Transcripts
|
|
200
|
+
|
|
201
|
+
Declare canonical transcript requirements with:
|
|
202
|
+
|
|
203
|
+
```html
|
|
204
|
+
<fy-transcript src="assets/voice.transcript.json" words></fy-transcript>
|
|
205
|
+
```
|
|
115
206
|
|
|
116
|
-
|
|
207
|
+
`words` requires measured word timings. `check`, `still`, and `render` validate the transcript file. Visual presentation is the composition's responsibility; a controller can fetch the JSON and render timed text or karaoke. See [subtitles and karaoke](subtitles-karaoke.md).
|
|
117
208
|
|
|
118
209
|
## Loading assets
|
|
119
210
|
|
|
120
|
-
Remote data, images, fonts, and other assets must resolve during
|
|
211
|
+
Remote data, images, fonts, and other assets must resolve during the loading phase before frame production. The production browser blocks network requests after loading begins. A fetch from `update()` is a bug.
|
|
121
212
|
|
|
122
|
-
Images
|
|
213
|
+
Images declared in the document are awaited by the host. Static fonts should be loaded with `document.fonts.ready` or an explicit `FontFace` before frame production.
|
|
123
214
|
|
|
124
215
|
## Validation and inspection
|
|
125
216
|
|
|
@@ -128,8 +219,8 @@ npx frameyard check hello --json
|
|
|
128
219
|
npx frameyard still hello --frame 42 --out hello-42.png --json
|
|
129
220
|
```
|
|
130
221
|
|
|
131
|
-
`check`
|
|
222
|
+
`check` runs static validation before Chromium, then evaluates every frame and validates audio and declared transcripts without capturing or encoding. Read the structured `diagnostics` array. Do not replace `check` with a single still.
|
|
132
223
|
|
|
133
224
|
## Metadata is fixed
|
|
134
225
|
|
|
135
|
-
`width`, `height`, `fps`, and `
|
|
226
|
+
`width`, `height`, `fps`, and `duration-in-frames` cannot be derived from props. Choose a reusable resolution, frame rate, and duration; put variation in props. Frameyard does not change duration to fit audio.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Diagnostics
|
|
2
|
+
|
|
3
|
+
Frameyard reports composition problems as structured diagnostics before and during frame production.
|
|
4
|
+
|
|
5
|
+
## Static validation
|
|
6
|
+
|
|
7
|
+
`check`, `still`, and `render` run a Node-side static composition validation before launching Chromium. `dev` runs it when a composition is requested. Static errors fail the command without starting a browser; warnings allow frame evaluation.
|
|
8
|
+
|
|
9
|
+
Static validation covers:
|
|
10
|
+
|
|
11
|
+
- doctype, root count, metadata, and default-props JSON
|
|
12
|
+
- controller declaration and file existence
|
|
13
|
+
- executable scripts, `on*` handlers, `javascript:` URLs, and unsupported visual elements
|
|
14
|
+
- unknown `data-fy-*` attributes, binding paths, `if`/`repeat` placement, and animation attributes
|
|
15
|
+
- audio/video/transcript references and ordinary image/stylesheet assets
|
|
16
|
+
- visual content outside `<fy-composition>`
|
|
17
|
+
|
|
18
|
+
## Diagnostic shape
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"severity": "warning",
|
|
23
|
+
"code": "BINDING_PATH_MISSING",
|
|
24
|
+
"message": "data-fy-text references prop \"titel\", which is not in the resolved props.",
|
|
25
|
+
"details": {
|
|
26
|
+
"path": "compositions/hello.html",
|
|
27
|
+
"line": 12,
|
|
28
|
+
"column": 5,
|
|
29
|
+
"element": "h1",
|
|
30
|
+
"attribute": "data-fy-text",
|
|
31
|
+
"value": "titel",
|
|
32
|
+
"suggestion": "title"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`details` may include `path`, `line`, `column`, `endLine`, `endColumn`, `element`, `attribute`, `value`, `suggestion`, `frame`, and `controller`.
|
|
38
|
+
|
|
39
|
+
## Common codes
|
|
40
|
+
|
|
41
|
+
| Code | Severity | Meaning |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| `COMPOSITION_INVALID` | error | Missing/invalid doctype, root, metadata, or document structure |
|
|
44
|
+
| `PROPS_INVALID` | error | Invalid default props JSON, invalid overrides, or `validateProps` failure |
|
|
45
|
+
| `CONTROLLER_INVALID` | error | Missing/duplicate/both-inline-and-external controller, missing file, or invalid exports |
|
|
46
|
+
| `BINDING_INVALID` | error | Unknown `data-fy-*` attribute or malformed binding path |
|
|
47
|
+
| `BINDING_PATH_MISSING` | warning | A binding path is absent from the resolved props |
|
|
48
|
+
| `BINDING_VALUE_INVALID` | warning | A text/attribute/class binding resolved to an object or array |
|
|
49
|
+
| `BINDING_REPEAT_INVALID` | error | Repeat is not on `<template>`, or its path is missing/not an array |
|
|
50
|
+
| `BINDING_REPEAT_NESTED` | error | Nested `data-fy-repeat` |
|
|
51
|
+
| `BINDING_TARGET_INVALID` | error | `data-fy-text` on a void element or the root; invalid binding target |
|
|
52
|
+
| `ANIMATION_INVALID` | error | Invalid `data-fy-anim-*` value |
|
|
53
|
+
| `ANIMATION_NOT_FOUND` | warning | `data-fy-anim-*` is present but the element has no CSS animation |
|
|
54
|
+
| `ANIMATION_MULTIPLE` | warning | Multiple animations on one declaratively scheduled element |
|
|
55
|
+
| `SCRIPT_NOT_ALLOWED` | error | Executable `<script>` other than the controller |
|
|
56
|
+
| `EVENT_HANDLER_NOT_ALLOWED` | error | Inline `on*` handler or `data-fy-attr-on*` |
|
|
57
|
+
| `URL_NOT_ALLOWED` | error | `javascript:` URL |
|
|
58
|
+
| `ELEMENT_NOT_SUPPORTED` | error | `iframe`, `object`, `embed`, `base`, refresh/CSP meta, `source`, `track`, or declarative shadow DOM |
|
|
59
|
+
| `ELEMENT_UNKNOWN` | warning | Unknown `fy-*` element |
|
|
60
|
+
| `CONTENT_OUTSIDE_ROOT` | warning | Visual content outside `<fy-composition>` |
|
|
61
|
+
| `MEDIA_INVALID` | error | Invalid or missing audio/video source or attributes |
|
|
62
|
+
| `TRANSCRIPT_INVALID` | error | Invalid or missing transcript source |
|
|
63
|
+
| `ASSET_NOT_FOUND` | error | Missing image, stylesheet, media, or transcript file |
|
|
64
|
+
| `AUDIO_WARNING` | warning | Audio timeline overrun or silent embedded media |
|
|
65
|
+
| `TRANSCRIPT_WARNING` | warning | Empty transcript text or no timed segments |
|
|
66
|
+
| `DETERMINISM_WARNING` | warning | Forbidden clock/randomness, CSS transition, or runtime diagnostic |
|
|
67
|
+
| `COMPOSITION_NOT_READY` | error | `seek`/`settle` before a successful load |
|
|
68
|
+
| `COMPOSITION_DISPOSED` | error | Operation after the host was disposed |
|
|
69
|
+
|
|
70
|
+
## Results
|
|
71
|
+
|
|
72
|
+
`check`, `still`, and `render` JSON results expose:
|
|
73
|
+
|
|
74
|
+
- `entry`: project-relative HTML path
|
|
75
|
+
- `controller`: `{ kind: "inline" }`, `{ kind: "external", src }`, or `null`
|
|
76
|
+
- `diagnostics`: all static and runtime diagnostics, deduplicated
|
|
77
|
+
|
|
78
|
+
`check` also exposes `metadata`, `framesChecked`, `audio.details`, and `transcripts.details`. `still` exposes `frame`, `path`, `width`, and `height`. `render` exposes the output path, dimensions, frame count, concurrency, bytes, and audio/transcript clip counts.
|
|
79
|
+
|
|
80
|
+
Failures use the same envelope with `error.code`, `error.message`, optional `error.details`, and top-level `diagnostics`.
|
|
81
|
+
|
|
82
|
+
## Working with diagnostics
|
|
83
|
+
|
|
84
|
+
1. Run `npx frameyard check <id> --json`.
|
|
85
|
+
2. Read every error first; fix the first one and rerun.
|
|
86
|
+
3. Treat warnings as actionable. `BINDING_PATH_MISSING` is usually a typo; use the `suggestion` field.
|
|
87
|
+
4. Use `details.path`, `line`, and `column` to edit the exact source.
|
|
88
|
+
5. Re-run `check`, inspect representative stills, then render.
|
|
89
|
+
|
|
90
|
+
Do not ignore a failing `check`, a missing still, or a nonzero render. Do not describe visual correctness you did not inspect.
|