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.
- package/CHANGELOG.md +164 -7
- package/GLOSSARY.md +152 -18
- package/README.md +328 -285
- package/dist/assets/integrations.d.ts +8 -4
- package/dist/assets/integrations.d.ts.map +1 -1
- package/dist/assets/integrations.js +8 -4
- package/dist/assets/integrations.js.map +1 -1
- package/dist/assets/provenance.d.ts +2 -2
- package/dist/assets/provenance.d.ts.map +1 -1
- package/dist/assets/provenance.js +5 -3
- package/dist/assets/provenance.js.map +1 -1
- package/dist/assets/tts-supertonic.d.ts +28 -0
- package/dist/assets/tts-supertonic.d.ts.map +1 -0
- package/dist/assets/tts-supertonic.js +156 -0
- package/dist/assets/tts-supertonic.js.map +1 -0
- package/dist/assets/tts.d.ts +18 -6
- package/dist/assets/tts.d.ts.map +1 -1
- package/dist/assets/tts.js +219 -97
- package/dist/assets/tts.js.map +1 -1
- package/dist/cli.js +1705 -106
- package/dist/cli.js.map +1 -1
- package/dist/composition/descriptor.d.ts +17 -3
- package/dist/composition/descriptor.d.ts.map +1 -1
- package/dist/composition/descriptor.js +51 -28
- package/dist/composition/descriptor.js.map +1 -1
- package/dist/composition/format.d.ts +12 -0
- package/dist/composition/format.d.ts.map +1 -0
- package/dist/composition/format.js +28 -0
- package/dist/composition/format.js.map +1 -0
- package/dist/composition/index.d.ts +2 -1
- package/dist/composition/index.d.ts.map +1 -1
- package/dist/composition/index.js +1 -0
- package/dist/composition/index.js.map +1 -1
- package/dist/context.d.ts +82 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +277 -0
- package/dist/context.js.map +1 -0
- package/dist/contracts.d.ts +80 -6
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +3 -0
- package/dist/contracts.js.map +1 -1
- package/dist/doctor.d.ts +14 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +166 -7
- package/dist/doctor.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/fonts/add.d.ts +41 -0
- package/dist/fonts/add.d.ts.map +1 -0
- package/dist/fonts/add.js +259 -0
- package/dist/fonts/add.js.map +1 -0
- package/dist/fonts/binary.d.ts +4 -0
- package/dist/fonts/binary.d.ts.map +1 -0
- package/dist/fonts/binary.js +21 -0
- package/dist/fonts/binary.js.map +1 -0
- package/dist/fonts/css.d.ts +4 -0
- package/dist/fonts/css.d.ts.map +1 -0
- package/dist/fonts/css.js +50 -0
- package/dist/fonts/css.js.map +1 -0
- package/dist/fonts/fontsource.d.ts +107 -0
- package/dist/fonts/fontsource.d.ts.map +1 -0
- package/dist/fonts/fontsource.js +565 -0
- package/dist/fonts/fontsource.js.map +1 -0
- package/dist/fonts/index.d.ts +11 -0
- package/dist/fonts/index.d.ts.map +1 -0
- package/dist/fonts/index.js +11 -0
- package/dist/fonts/index.js.map +1 -0
- package/dist/fonts/install.d.ts +24 -0
- package/dist/fonts/install.d.ts.map +1 -0
- package/dist/fonts/install.js +145 -0
- package/dist/fonts/install.js.map +1 -0
- package/dist/fonts/manifest.d.ts +21 -0
- package/dist/fonts/manifest.d.ts.map +1 -0
- package/dist/fonts/manifest.js +96 -0
- package/dist/fonts/manifest.js.map +1 -0
- package/dist/fonts/options.d.ts +9 -0
- package/dist/fonts/options.d.ts.map +1 -0
- package/dist/fonts/options.js +66 -0
- package/dist/fonts/options.js.map +1 -0
- package/dist/fonts/schema.d.ts +4 -0
- package/dist/fonts/schema.d.ts.map +1 -0
- package/dist/fonts/schema.js +316 -0
- package/dist/fonts/schema.js.map +1 -0
- package/dist/fonts/summary.d.ts +16 -0
- package/dist/fonts/summary.d.ts.map +1 -0
- package/dist/fonts/summary.js +22 -0
- package/dist/fonts/summary.js.map +1 -0
- package/dist/fonts/types.d.ts +56 -0
- package/dist/fonts/types.d.ts.map +1 -0
- package/dist/fonts/types.js +8 -0
- package/dist/fonts/types.js.map +1 -0
- package/dist/fonts/verify.d.ts +33 -0
- package/dist/fonts/verify.d.ts.map +1 -0
- package/dist/fonts/verify.js +200 -0
- package/dist/fonts/verify.js.map +1 -0
- package/dist/html/document-adapter.d.ts +3 -3
- package/dist/html/document-adapter.d.ts.map +1 -1
- package/dist/html/document-adapter.js +1 -1
- package/dist/html/document-adapter.js.map +1 -1
- package/dist/html/index.d.ts +1 -1
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/parse.d.ts +10 -2
- package/dist/html/parse.d.ts.map +1 -1
- package/dist/html/parse.js +14 -5
- package/dist/html/parse.js.map +1 -1
- package/dist/html/validate.d.ts +12 -1
- package/dist/html/validate.d.ts.map +1 -1
- package/dist/html/validate.js +292 -61
- package/dist/html/validate.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +61 -0
- package/dist/inspect.d.ts.map +1 -0
- package/dist/inspect.js +325 -0
- package/dist/inspect.js.map +1 -0
- package/dist/lint.d.ts +63 -0
- package/dist/lint.d.ts.map +1 -0
- package/dist/lint.js +265 -0
- package/dist/lint.js.map +1 -0
- package/dist/policy.d.ts +69 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +361 -0
- package/dist/policy.js.map +1 -0
- package/dist/preview/main.d.ts +2 -2
- package/dist/preview/main.d.ts.map +1 -1
- package/dist/preview/main.js +35 -14
- package/dist/preview/main.js.map +1 -1
- package/dist/preview/timeline-data.d.ts +17 -65
- package/dist/preview/timeline-data.d.ts.map +1 -1
- package/dist/preview/timeline-host.d.ts.map +1 -1
- package/dist/preview/timeline-host.js +73 -38
- package/dist/preview/timeline-host.js.map +1 -1
- package/dist/preview/timeline-main.d.ts.map +1 -1
- package/dist/preview/timeline-main.js +47 -3
- package/dist/preview/timeline-main.js.map +1 -1
- package/dist/project.d.ts +13 -8
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +20 -17
- package/dist/project.js.map +1 -1
- package/dist/renderer/browser.d.ts +2 -2
- package/dist/renderer/browser.d.ts.map +1 -1
- package/dist/renderer/browser.js +10 -4
- package/dist/renderer/browser.js.map +1 -1
- package/dist/renderer/ffmpeg.d.ts +44 -1
- package/dist/renderer/ffmpeg.d.ts.map +1 -1
- package/dist/renderer/ffmpeg.js +371 -73
- package/dist/renderer/ffmpeg.js.map +1 -1
- package/dist/renderer/index.d.ts +40 -4
- package/dist/renderer/index.d.ts.map +1 -1
- package/dist/renderer/index.js +100 -25
- package/dist/renderer/index.js.map +1 -1
- package/dist/renderer/keyframe-expressions.d.ts +39 -0
- package/dist/renderer/keyframe-expressions.d.ts.map +1 -0
- package/dist/renderer/keyframe-expressions.js +180 -0
- package/dist/renderer/keyframe-expressions.js.map +1 -0
- package/dist/renderer/timeline.d.ts +2 -0
- package/dist/renderer/timeline.d.ts.map +1 -1
- package/dist/renderer/timeline.js +168 -106
- package/dist/renderer/timeline.js.map +1 -1
- package/dist/runtime/host.d.ts.map +1 -1
- package/dist/runtime/host.js +159 -31
- package/dist/runtime/host.js.map +1 -1
- package/dist/scaffold.d.ts +3 -0
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +48 -317
- package/dist/scaffold.js.map +1 -1
- package/dist/server/composition-html.d.ts +15 -7
- package/dist/server/composition-html.d.ts.map +1 -1
- package/dist/server/composition-html.js +72 -16
- package/dist/server/composition-html.js.map +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +56 -6
- package/dist/server/server.js.map +1 -1
- package/dist/server/timeline-preview-data.d.ts.map +1 -1
- package/dist/server/timeline-preview-data.js +12 -23
- package/dist/server/timeline-preview-data.js.map +1 -1
- package/dist/server/timeline-preview-html.d.ts.map +1 -1
- package/dist/server/timeline-preview-html.js +20 -3
- package/dist/server/timeline-preview-html.js.map +1 -1
- package/dist/templates.d.ts +16 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +186 -0
- package/dist/templates.js.map +1 -0
- package/dist/timeline/easing.d.ts +36 -0
- package/dist/timeline/easing.d.ts.map +1 -0
- package/dist/timeline/easing.js +225 -0
- package/dist/timeline/easing.js.map +1 -0
- package/dist/timeline/keyframes.d.ts +23 -0
- package/dist/timeline/keyframes.d.ts.map +1 -0
- package/dist/timeline/keyframes.js +95 -0
- package/dist/timeline/keyframes.js.map +1 -0
- package/dist/timeline/plan.d.ts +6 -12
- package/dist/timeline/plan.d.ts.map +1 -1
- package/dist/timeline/plan.js +17 -30
- package/dist/timeline/plan.js.map +1 -1
- package/dist/timeline/resolve.d.ts +16 -3
- package/dist/timeline/resolve.d.ts.map +1 -1
- package/dist/timeline/resolve.js +119 -13
- package/dist/timeline/resolve.js.map +1 -1
- package/dist/timeline/scene.d.ts +126 -0
- package/dist/timeline/scene.d.ts.map +1 -0
- package/dist/timeline/scene.js +109 -0
- package/dist/timeline/scene.js.map +1 -0
- package/dist/timeline/schema.d.ts +38 -0
- package/dist/timeline/schema.d.ts.map +1 -1
- package/dist/timeline/schema.js +290 -9
- package/dist/timeline/schema.js.map +1 -1
- package/dist/timeline/transitions.d.ts +21 -0
- package/dist/timeline/transitions.d.ts.map +1 -0
- package/dist/timeline/transitions.js +23 -0
- package/dist/timeline/transitions.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/adr/0007-make-html-documents-the-composition-unit.md +2 -2
- package/docs/adr/0008-share-the-composition-document-grammar.md +1 -1
- package/docs/adr/0010-make-timelines-the-render-unit.md +3 -2
- package/docs/adr/0011-compose-timeline-frames-in-a-live-browser-host.md +1 -1
- package/docs/adr/0012-render-timelines-with-segmented-lossless-clip-intermediates.md +1 -1
- package/docs/adr/0014-compose-timeline-clips-with-css-blend-modes.md +38 -0
- package/docs/adr/0015-inherit-composition-format-from-the-project.md +28 -0
- package/docs/adr/0016-author-compositions-as-html-fragments.md +32 -0
- package/docs/adr/0017-render-timeless-compositions-as-stills.md +99 -0
- package/docs/adr/0018-animate-timeline-clips-with-keyframes.md +35 -0
- package/docs/adr/0019-make-same-track-overlap-the-transition-window.md +36 -0
- package/docs/adr/0020-crossfade-audio-clips-on-audio-tracks.md +27 -0
- package/docs/adr/0021-project-timeline-scenes-once.md +32 -0
- package/docs/adr/0022-gate-heavy-operations-with-a-workspace-resource-policy.md +35 -0
- package/docs/adr/0023-pin-fonts-as-project-assets.md +98 -0
- package/docs/migration-0.4.md +5 -3
- package/docs/migration-0.8.md +163 -0
- package/docs/preview-testing.md +23 -3
- package/docs/{v0.1-design.md → v0.x-design.md} +86 -53
- package/examples/blend/README.md +106 -0
- package/examples/blend/assets/ink.mp4 +0 -0
- package/examples/blend/compositions/aurora.html +55 -0
- package/examples/blend/compositions/backdrop.html +74 -0
- package/examples/blend/compositions/labels.html +149 -0
- package/examples/blend/fy.config.js +11 -0
- package/examples/blend/scripts/make-ink.sh +26 -0
- package/examples/blend/timelines/blend.timeline.json +155 -0
- package/examples/demo/README.md +2 -2
- package/examples/demo/compositions/demo.html +243 -251
- package/examples/intro/README.md +71 -0
- package/examples/intro/assets/fonts/figtree/LICENSE.txt +93 -0
- package/examples/intro/assets/fonts/figtree/figtree-latin-wght-normal.woff2 +0 -0
- package/examples/intro/assets/fonts/instrument-serif/LICENSE.txt +93 -0
- package/examples/intro/assets/fonts/instrument-serif/instrument-serif-latin-400-italic.woff2 +0 -0
- package/examples/intro/assets/fonts/space-mono/LICENSE.txt +93 -0
- package/examples/intro/assets/fonts/space-mono/space-mono-latin-400-normal.woff2 +0 -0
- package/examples/intro/assets/fonts/space-mono/space-mono-latin-700-normal.woff2 +0 -0
- package/examples/intro/assets/grain.png +0 -0
- package/examples/intro/assets/score.ogg +0 -0
- package/examples/intro/compositions/brand.html +412 -0
- package/examples/intro/compositions/chrome.controller.js +63 -0
- package/examples/intro/compositions/chrome.html +256 -0
- package/examples/intro/compositions/lattice.controller.js +148 -0
- package/examples/intro/compositions/lattice.html +308 -0
- package/examples/intro/compositions/opening.html +504 -0
- package/examples/intro/compositions/studio.html +930 -0
- package/examples/intro/compositions/theme.css +218 -0
- package/examples/intro/fy.config.js +13 -0
- package/examples/intro/fy.fonts.json +100 -0
- package/examples/intro/scripts/make-grain.mjs +79 -0
- package/examples/intro/scripts/make-score.mjs +702 -0
- package/examples/intro/timelines/intro.timeline.json +60 -0
- package/examples/keyframes/README.md +76 -0
- package/examples/keyframes/compositions/backdrop.html +60 -0
- package/examples/keyframes/compositions/card.html +99 -0
- package/examples/keyframes/fy.config.js +10 -0
- package/examples/keyframes/timelines/keyframes.timeline.json +60 -0
- package/examples/nested/README.md +112 -0
- package/examples/nested/assets/pulse.wav +0 -0
- package/examples/nested/compositions/backdrop.html +84 -0
- package/examples/nested/compositions/card.html +275 -0
- package/examples/nested/compositions/overlay.html +307 -0
- package/examples/nested/fy.config.js +13 -0
- package/examples/nested/scripts/make-pulse.sh +20 -0
- package/examples/nested/timelines/chapter-a.timeline.json +39 -0
- package/examples/nested/timelines/chapter-b.timeline.json +39 -0
- package/examples/nested/timelines/nested-reel.timeline.json +52 -0
- package/examples/static/README.md +17 -0
- package/examples/static/compositions/still.html +37 -0
- package/examples/static/fy.config.js +9 -0
- package/examples/static/timelines/still.timeline.json +13 -0
- package/examples/transitions/README.md +128 -0
- package/examples/transitions/assets/alpha.wav +0 -0
- package/examples/transitions/assets/beta.wav +0 -0
- package/examples/transitions/assets/gamma.wav +0 -0
- package/examples/transitions/compositions/alpha.html +56 -0
- package/examples/transitions/compositions/beta.html +56 -0
- package/examples/transitions/compositions/gamma.html +56 -0
- package/examples/transitions/fy.config.js +12 -0
- package/examples/transitions/timelines/audio-crossfade.timeline.json +44 -0
- package/examples/transitions/timelines/transitions.timeline.json +31 -0
- package/examples/video/README.md +4 -4
- package/examples/video/compositions/video-mp4.html +349 -357
- package/examples/video/compositions/video-overlay.html +337 -345
- package/examples/video/compositions/video.html +349 -357
- package/examples/webgl/README.md +1 -1
- package/examples/webgl/compositions/webgl.html +205 -213
- package/package.json +13 -5
- package/scaffolds/init/AGENTS.md +131 -0
- package/scaffolds/init/README.md +56 -0
- package/scaffolds/init/compositions/hello.html +79 -0
- package/scaffolds/init/fy.config.js +9 -0
- package/scaffolds/init/gitignore.template +1 -0
- package/scaffolds/init/package.json +11 -0
- package/scaffolds/init/props/hello.json +4 -0
- package/scaffolds/init/props/main.json +6 -0
- package/scaffolds/init/timelines/main.timeline.json +20 -0
- package/scaffolds/local-models/LOCAL-MODELS.md +77 -0
- package/scaffolds/local-models/fy.policy.json +5 -0
- package/scaffolds/local-models/opencode/opencode.jsonc +17 -0
- package/scaffolds/local-models/pi/APPEND_SYSTEM.md +21 -0
- package/scaffolds/local-models/pi/sandbox.json +15 -0
- package/scaffolds/local-models/pi/settings.json +15 -0
- package/skills/frameyard/README.md +1 -0
- package/skills/frameyard/SKILL.md +59 -59
- package/skills/frameyard/references/audio-assets.md +32 -3
- package/skills/frameyard/references/common-mistakes.md +81 -7
- package/skills/frameyard/references/controllers.md +5 -4
- package/skills/frameyard/references/deterministic-authoring.md +49 -37
- package/skills/frameyard/references/diagnostics.md +42 -4
- package/skills/frameyard/references/failure-recovery.md +56 -10
- package/skills/frameyard/references/fonts.md +132 -0
- package/skills/frameyard/references/json-results.md +165 -10
- package/skills/frameyard/references/quick-reference.md +222 -0
- package/skills/frameyard/references/timelines.md +109 -15
- /package/examples/demo/{video.config.js → fy.config.js} +0 -0
- /package/examples/video/{video.config.js → fy.config.js} +0 -0
- /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,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>
|