frameyard 0.5.0 → 0.7.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 +100 -0
- package/GLOSSARY.md +152 -24
- package/README.md +99 -31
- package/dist/assets/process.js.map +1 -1
- package/dist/assets/provenance.js.map +1 -1
- package/dist/cli.js +326 -40
- package/dist/cli.js.map +1 -1
- package/dist/composition/attributes.d.ts.map +1 -1
- package/dist/composition/diagnostics.js.map +1 -1
- package/dist/contracts.d.ts +12 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +79 -0
- 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/html/validate.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/preview/main.js +96 -11
- package/dist/preview/main.js.map +1 -1
- package/dist/preview/timeline-data.d.ts +85 -0
- package/dist/preview/timeline-data.d.ts.map +1 -0
- package/dist/preview/timeline-data.js +2 -0
- package/dist/preview/timeline-data.js.map +1 -0
- package/dist/preview/timeline-host.d.ts +21 -0
- package/dist/preview/timeline-host.d.ts.map +1 -0
- package/dist/preview/timeline-host.js +308 -0
- package/dist/preview/timeline-host.js.map +1 -0
- package/dist/preview/timeline-main.d.ts +17 -0
- package/dist/preview/timeline-main.d.ts.map +1 -0
- package/dist/preview/timeline-main.js +368 -0
- package/dist/preview/timeline-main.js.map +1 -0
- package/dist/project.d.ts +16 -1
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +81 -26
- package/dist/project.js.map +1 -1
- package/dist/renderer/browser.d.ts +2 -0
- package/dist/renderer/browser.d.ts.map +1 -1
- package/dist/renderer/browser.js +6 -1
- package/dist/renderer/browser.js.map +1 -1
- package/dist/renderer/ffmpeg.d.ts +126 -1
- package/dist/renderer/ffmpeg.d.ts.map +1 -1
- package/dist/renderer/ffmpeg.js +450 -6
- package/dist/renderer/ffmpeg.js.map +1 -1
- package/dist/renderer/index.d.ts +28 -3
- package/dist/renderer/index.d.ts.map +1 -1
- package/dist/renderer/index.js +138 -38
- package/dist/renderer/index.js.map +1 -1
- package/dist/renderer/timeline.d.ts +146 -0
- package/dist/renderer/timeline.d.ts.map +1 -0
- package/dist/renderer/timeline.js +1098 -0
- package/dist/renderer/timeline.js.map +1 -0
- package/dist/runtime/host.js.map +1 -1
- package/dist/runtime/media.d.ts.map +1 -1
- package/dist/runtime/media.js +3 -6
- package/dist/runtime/media.js.map +1 -1
- package/dist/runtime/video.d.ts +13 -0
- package/dist/runtime/video.d.ts.map +1 -0
- package/dist/runtime/video.js +41 -0
- package/dist/runtime/video.js.map +1 -0
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +90 -24
- package/dist/scaffold.js.map +1 -1
- package/dist/server/composition-html.d.ts.map +1 -1
- package/dist/server/preview-html.d.ts.map +1 -1
- package/dist/server/preview-html.js +15 -1
- package/dist/server/preview-html.js.map +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +39 -1
- package/dist/server/server.js.map +1 -1
- package/dist/server/timeline-preview-data.d.ts +4 -0
- package/dist/server/timeline-preview-data.d.ts.map +1 -0
- package/dist/server/timeline-preview-data.js +97 -0
- package/dist/server/timeline-preview-data.js.map +1 -0
- package/dist/server/timeline-preview-html.d.ts +4 -0
- package/dist/server/timeline-preview-html.d.ts.map +1 -0
- package/dist/server/timeline-preview-html.js +227 -0
- package/dist/server/timeline-preview-html.js.map +1 -0
- package/dist/timeline/plan.d.ts +32 -0
- package/dist/timeline/plan.d.ts.map +1 -0
- package/dist/timeline/plan.js +68 -0
- package/dist/timeline/plan.js.map +1 -0
- package/dist/timeline/resolve.d.ts +60 -0
- package/dist/timeline/resolve.d.ts.map +1 -0
- package/dist/timeline/resolve.js +523 -0
- package/dist/timeline/resolve.js.map +1 -0
- package/dist/timeline/schema.d.ts +71 -0
- package/dist/timeline/schema.d.ts.map +1 -0
- package/dist/timeline/schema.js +425 -0
- package/dist/timeline/schema.js.map +1 -0
- package/dist/transcript.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/adr/0002-make-frame-and-props-the-only-animation-state.md +2 -0
- package/docs/adr/0005-keep-parallel-rendering-opt-in-and-frame-ordered.md +2 -0
- package/docs/adr/0007-make-html-documents-the-composition-unit.md +1 -1
- package/docs/adr/0009-composite-production-frames-in-software.md +1 -1
- package/docs/adr/0010-make-timelines-the-render-unit.md +24 -0
- package/docs/adr/0011-compose-timeline-frames-in-a-live-browser-host.md +22 -0
- package/docs/adr/0012-render-timelines-with-segmented-lossless-clip-intermediates.md +31 -0
- package/docs/adr/0013-capture-bounded-multi-frame-stills-for-inspection.md +33 -0
- package/docs/preview-testing.md +53 -0
- package/docs/v0.1-design.md +138 -87
- package/examples/demo/README.md +10 -3
- package/examples/demo/timelines/demo.timeline.json +12 -0
- package/examples/demo/video.config.js +4 -0
- package/examples/video/README.md +50 -6
- package/examples/video/compositions/video-overlay.html +350 -0
- package/examples/video/props/video-timeline.json +10 -0
- package/examples/video/timelines/video-timeline-mp4.timeline.json +44 -0
- package/examples/video/timelines/video-timeline.timeline.json +44 -0
- package/examples/video/video.config.js +6 -0
- package/package.json +5 -3
- package/skills/frameyard/README.md +7 -5
- package/skills/frameyard/SKILL.md +19 -13
- package/skills/frameyard/references/common-mistakes.md +1 -0
- package/skills/frameyard/references/deterministic-authoring.md +2 -1
- package/skills/frameyard/references/diagnostics.md +1 -1
- package/skills/frameyard/references/failure-recovery.md +3 -2
- package/skills/frameyard/references/json-results.md +15 -6
- package/skills/frameyard/references/subtitles-karaoke.md +2 -2
- package/skills/frameyard/references/timelines.md +143 -0
- package/skills/frameyard/scripts/frameyard.mjs +20 -3
package/examples/demo/README.md
CHANGED
|
@@ -10,14 +10,21 @@ A 30-second, 1920×1080 HTML composition that exercises the current core:
|
|
|
10
10
|
- frame-state CSS variables for the progress bar
|
|
11
11
|
|
|
12
12
|
The composition lives in `compositions/demo.html`; the optional controller is
|
|
13
|
-
`compositions/demo.controller.js`.
|
|
13
|
+
`compositions/demo.controller.js`. `timelines/demo.timeline.json` wraps the
|
|
14
|
+
composition as a clip in a 1920×1080, 30 fps timeline.
|
|
14
15
|
|
|
15
16
|
From the repository root:
|
|
16
17
|
|
|
17
18
|
```sh
|
|
18
19
|
pnpm build
|
|
19
|
-
node dist/cli.js check demo --project examples/demo
|
|
20
|
-
node dist/cli.js still demo --frame 240 --project examples/demo --out demo-still.png
|
|
20
|
+
node dist/cli.js check demo-timeline --project examples/demo
|
|
21
|
+
node dist/cli.js still demo-timeline --frame 240 --project examples/demo --out demo-still.png
|
|
22
|
+
node dist/cli.js render demo-timeline --project examples/demo --props examples/demo/props/demo.json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The composition remains renderable on its own:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
21
28
|
node dist/cli.js render demo --project examples/demo --props examples/demo/props/demo.json
|
|
22
29
|
```
|
|
23
30
|
|
package/examples/video/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Frameyard video example
|
|
2
2
|
|
|
3
|
-
A 12-second, 1920×1080
|
|
4
|
-
|
|
3
|
+
A 12-second, 1920×1080 project that treats a local clip as source footage. The
|
|
4
|
+
project includes the same layout for two source codecs:
|
|
5
5
|
|
|
6
6
|
- `video` reads `assets/clip.webm` (VP9 + Opus)
|
|
7
7
|
- `video-mp4` reads `assets/clip.mp4` (H.264 + AAC)
|
|
@@ -25,11 +25,49 @@ controller is needed. The header, copy, and footer fade individually rather
|
|
|
25
25
|
than the whole scene; a full-scene opacity animation above a video can leave
|
|
26
26
|
the load frame on screen in interactive preview after a large seek.
|
|
27
27
|
|
|
28
|
+
## Timeline with the video on a track
|
|
29
|
+
|
|
30
|
+
`video-timeline` and `video-timeline-mp4` move the footage out of the
|
|
31
|
+
composition and onto timeline tracks. The HTML is now a transparent overlay
|
|
32
|
+
above it:
|
|
33
|
+
|
|
34
|
+
- `v1` holds the hero clip: the whole 12-second source, audio at `0.85`
|
|
35
|
+
- `v2` holds the detail pass from source frame 174 (5.8 s) for 150 frames,
|
|
36
|
+
muted, placed in a 440×248 transform box in the bottom right
|
|
37
|
+
- `v3` holds `video-overlay`, an HTML-only composition with no `<video>`
|
|
38
|
+
elements and a transparent document background
|
|
39
|
+
|
|
40
|
+
`video-timeline` reads `assets/clip.webm` (VP9 + Opus) and
|
|
41
|
+
`video-timeline-mp4` reads `assets/clip.mp4` (H.264 + AAC). Both share the same
|
|
42
|
+
overlay composition and props file; the overlay clip sets its own `format` prop
|
|
43
|
+
so the header reports the codec of each timeline.
|
|
44
|
+
|
|
45
|
+
The detail clip uses `transform.width` and `transform.height` instead of
|
|
46
|
+
`scale`, with `fit: "cover"` filling that box. The box matches the overlay's
|
|
47
|
+
detail window exactly, so the video and its chrome stay aligned in production
|
|
48
|
+
and preview.
|
|
49
|
+
|
|
50
|
+
The overlay draws the scrim, typography, frame border, detail-window chrome,
|
|
51
|
+
progress bar, and outro fade. `video-overlay` declares its own frame animations,
|
|
52
|
+
so the title and footer still animate on the timeline clock. Production captures
|
|
53
|
+
the overlay with alpha and composites it over the video layers with FFmpeg; the
|
|
54
|
+
interactive timeline preview composites the same layers in Chromium.
|
|
55
|
+
|
|
56
|
+
Both timelines accept `props/video-timeline.json`, which overrides the overlay
|
|
57
|
+
composition's props. Timeline `--props` is keyed by composition clip id, so the
|
|
58
|
+
file targets the `overlay` clip.
|
|
59
|
+
|
|
60
|
+
Open the preview with `node dist/cli.js dev --project examples/video`; the
|
|
61
|
+
compositions and both timelines are listed.
|
|
62
|
+
|
|
28
63
|
The compositions live in `compositions/video.html` and
|
|
29
|
-
`compositions/video-mp4.html
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
64
|
+
`compositions/video-mp4.html`; the overlay lives in
|
|
65
|
+
`compositions/video-overlay.html`, and the timelines in
|
|
66
|
+
`timelines/video-timeline.timeline.json` and
|
|
67
|
+
`timelines/video-timeline-mp4.timeline.json`. Both clips are 12-second 1280×720
|
|
68
|
+
files generated procedurally for this example; the MP4 is transcoded from the
|
|
69
|
+
WebM source. Replace either asset with a local, seekable video file that is at
|
|
70
|
+
least 12 seconds long; remote URLs and live streams are not supported.
|
|
33
71
|
|
|
34
72
|
Regenerate both clips from scratch with the bundled FFmpeg script:
|
|
35
73
|
|
|
@@ -48,10 +86,16 @@ From the repository root:
|
|
|
48
86
|
pnpm build
|
|
49
87
|
node dist/cli.js check video --project examples/video
|
|
50
88
|
node dist/cli.js check video-mp4 --project examples/video
|
|
89
|
+
node dist/cli.js check video-timeline --project examples/video
|
|
90
|
+
node dist/cli.js check video-timeline-mp4 --project examples/video
|
|
51
91
|
node dist/cli.js still video --frame 210 --project examples/video --out video-still.png
|
|
52
92
|
node dist/cli.js still video-mp4 --frame 210 --project examples/video --out video-mp4-still.png
|
|
93
|
+
node dist/cli.js still video-timeline --frame 210 --project examples/video --out video-timeline-still.png
|
|
94
|
+
node dist/cli.js still video-timeline-mp4 --frame 210 --project examples/video --out video-timeline-mp4-still.png
|
|
53
95
|
node dist/cli.js render video --project examples/video --props examples/video/props/video.json
|
|
54
96
|
node dist/cli.js render video-mp4 --project examples/video --props examples/video/props/video.json
|
|
97
|
+
node dist/cli.js render video-timeline --project examples/video --props examples/video/props/video-timeline.json
|
|
98
|
+
node dist/cli.js render video-timeline-mp4 --project examples/video --props examples/video/props/video-timeline.json
|
|
55
99
|
```
|
|
56
100
|
|
|
57
101
|
Start the interactive preview with:
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<script type="application/json" data-fy="props">
|
|
6
|
+
{
|
|
7
|
+
"kicker": "Frameyard / Timeline Overlay 001",
|
|
8
|
+
"title": "Timeline",
|
|
9
|
+
"titleAccent": "Overlay",
|
|
10
|
+
"subtitle": "The clip lives on a timeline track. This HTML layer composites over it with alpha.",
|
|
11
|
+
"detail": "Detail / second pass",
|
|
12
|
+
"format": "30 FPS",
|
|
13
|
+
"accent": "#ffc37a"
|
|
14
|
+
}
|
|
15
|
+
</script>
|
|
16
|
+
<style>
|
|
17
|
+
@keyframes fade-in {
|
|
18
|
+
from {
|
|
19
|
+
opacity: 0;
|
|
20
|
+
}
|
|
21
|
+
to {
|
|
22
|
+
opacity: 1;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
@keyframes rise {
|
|
26
|
+
from {
|
|
27
|
+
opacity: 0;
|
|
28
|
+
transform: translateY(42px);
|
|
29
|
+
}
|
|
30
|
+
to {
|
|
31
|
+
opacity: 1;
|
|
32
|
+
transform: translateY(0);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
@keyframes draw {
|
|
36
|
+
from {
|
|
37
|
+
opacity: 0;
|
|
38
|
+
transform: scaleX(0);
|
|
39
|
+
}
|
|
40
|
+
to {
|
|
41
|
+
opacity: 1;
|
|
42
|
+
transform: scaleX(1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
@keyframes detail-cycle {
|
|
46
|
+
0% {
|
|
47
|
+
opacity: 0;
|
|
48
|
+
}
|
|
49
|
+
6% {
|
|
50
|
+
opacity: 1;
|
|
51
|
+
}
|
|
52
|
+
94% {
|
|
53
|
+
opacity: 1;
|
|
54
|
+
}
|
|
55
|
+
100% {
|
|
56
|
+
opacity: 0;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
@keyframes outro-fade {
|
|
60
|
+
from {
|
|
61
|
+
opacity: 0;
|
|
62
|
+
}
|
|
63
|
+
to {
|
|
64
|
+
opacity: 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
html,
|
|
68
|
+
body,
|
|
69
|
+
fy-composition {
|
|
70
|
+
background: transparent;
|
|
71
|
+
}
|
|
72
|
+
#scene {
|
|
73
|
+
position: relative;
|
|
74
|
+
width: 100%;
|
|
75
|
+
height: 100%;
|
|
76
|
+
overflow: hidden;
|
|
77
|
+
isolation: isolate;
|
|
78
|
+
color: #eef4f1;
|
|
79
|
+
font-family: Inter, "Helvetica Neue", ui-sans-serif, system-ui, sans-serif;
|
|
80
|
+
}
|
|
81
|
+
#scrim {
|
|
82
|
+
position: absolute;
|
|
83
|
+
inset: 0;
|
|
84
|
+
background: linear-gradient(
|
|
85
|
+
90deg,
|
|
86
|
+
rgba(4, 8, 11, 0.94) 0%,
|
|
87
|
+
rgba(4, 8, 11, 0.82) 22%,
|
|
88
|
+
rgba(4, 8, 11, 0.35) 46%,
|
|
89
|
+
rgba(4, 8, 11, 0) 66%
|
|
90
|
+
);
|
|
91
|
+
pointer-events: none;
|
|
92
|
+
}
|
|
93
|
+
#footer-scrim {
|
|
94
|
+
position: absolute;
|
|
95
|
+
left: 0;
|
|
96
|
+
right: 0;
|
|
97
|
+
bottom: 0;
|
|
98
|
+
height: 150px;
|
|
99
|
+
background: linear-gradient(0deg, rgba(4, 8, 11, 0.88) 0%, rgba(4, 8, 11, 0) 100%);
|
|
100
|
+
pointer-events: none;
|
|
101
|
+
}
|
|
102
|
+
#frame-border {
|
|
103
|
+
position: absolute;
|
|
104
|
+
inset: 36px;
|
|
105
|
+
border: 1px solid rgba(238, 244, 241, 0.16);
|
|
106
|
+
pointer-events: none;
|
|
107
|
+
}
|
|
108
|
+
#header {
|
|
109
|
+
position: absolute;
|
|
110
|
+
top: 80px;
|
|
111
|
+
left: 148px;
|
|
112
|
+
right: 148px;
|
|
113
|
+
display: flex;
|
|
114
|
+
justify-content: space-between;
|
|
115
|
+
align-items: center;
|
|
116
|
+
gap: 32px;
|
|
117
|
+
animation: fade-in 1s linear both;
|
|
118
|
+
}
|
|
119
|
+
#kicker {
|
|
120
|
+
display: flex;
|
|
121
|
+
align-items: center;
|
|
122
|
+
gap: 15px;
|
|
123
|
+
font:
|
|
124
|
+
600 14px/1 ui-monospace,
|
|
125
|
+
monospace;
|
|
126
|
+
letter-spacing: 0.24em;
|
|
127
|
+
text-transform: uppercase;
|
|
128
|
+
color: var(--fy-prop-accent);
|
|
129
|
+
}
|
|
130
|
+
#kicker::before {
|
|
131
|
+
content: "";
|
|
132
|
+
width: 8px;
|
|
133
|
+
height: 8px;
|
|
134
|
+
background: var(--fy-prop-accent);
|
|
135
|
+
box-shadow: 0 0 16px var(--fy-prop-accent);
|
|
136
|
+
}
|
|
137
|
+
#format {
|
|
138
|
+
font:
|
|
139
|
+
500 12px/1 ui-monospace,
|
|
140
|
+
monospace;
|
|
141
|
+
letter-spacing: 0.16em;
|
|
142
|
+
text-transform: uppercase;
|
|
143
|
+
color: #b9c6c8;
|
|
144
|
+
text-shadow: 0 2px 18px rgba(0, 0, 0, 0.65);
|
|
145
|
+
}
|
|
146
|
+
#copy {
|
|
147
|
+
position: absolute;
|
|
148
|
+
top: 278px;
|
|
149
|
+
left: 148px;
|
|
150
|
+
width: 860px;
|
|
151
|
+
}
|
|
152
|
+
#title {
|
|
153
|
+
margin: 0;
|
|
154
|
+
font-size: 174px;
|
|
155
|
+
line-height: 0.84;
|
|
156
|
+
letter-spacing: -0.064em;
|
|
157
|
+
font-weight: 660;
|
|
158
|
+
}
|
|
159
|
+
#title span,
|
|
160
|
+
#title em {
|
|
161
|
+
display: block;
|
|
162
|
+
animation: rise 1s cubic-bezier(0.215, 0.61, 0.355, 1) both;
|
|
163
|
+
}
|
|
164
|
+
#title em {
|
|
165
|
+
margin-top: 0.06em;
|
|
166
|
+
font-family: "Iowan Old Style", "Palatino Linotype", Georgia, ui-serif, serif;
|
|
167
|
+
font-style: italic;
|
|
168
|
+
font-weight: 400;
|
|
169
|
+
letter-spacing: -0.035em;
|
|
170
|
+
color: var(--fy-prop-accent);
|
|
171
|
+
}
|
|
172
|
+
#subtitle {
|
|
173
|
+
width: 720px;
|
|
174
|
+
margin: 38px 0 0;
|
|
175
|
+
font-size: 29px;
|
|
176
|
+
line-height: 1.5;
|
|
177
|
+
font-weight: 420;
|
|
178
|
+
color: #b3c1c4;
|
|
179
|
+
animation: rise 1s cubic-bezier(0.215, 0.61, 0.355, 1) both;
|
|
180
|
+
}
|
|
181
|
+
#detail {
|
|
182
|
+
position: absolute;
|
|
183
|
+
left: 1332px;
|
|
184
|
+
top: 646px;
|
|
185
|
+
width: 440px;
|
|
186
|
+
animation: detail-cycle 1s linear both;
|
|
187
|
+
}
|
|
188
|
+
#detail-bar {
|
|
189
|
+
display: flex;
|
|
190
|
+
justify-content: space-between;
|
|
191
|
+
align-items: center;
|
|
192
|
+
gap: 16px;
|
|
193
|
+
height: 22px;
|
|
194
|
+
margin-bottom: 10px;
|
|
195
|
+
padding: 0 10px;
|
|
196
|
+
background: rgba(4, 8, 11, 0.55);
|
|
197
|
+
font:
|
|
198
|
+
600 11px/1 ui-monospace,
|
|
199
|
+
monospace;
|
|
200
|
+
letter-spacing: 0.18em;
|
|
201
|
+
text-transform: uppercase;
|
|
202
|
+
}
|
|
203
|
+
#detail-bar > span:first-child {
|
|
204
|
+
color: var(--fy-prop-accent);
|
|
205
|
+
}
|
|
206
|
+
#detail-time {
|
|
207
|
+
color: #c0cdcf;
|
|
208
|
+
font-weight: 500;
|
|
209
|
+
}
|
|
210
|
+
#detail-window {
|
|
211
|
+
position: relative;
|
|
212
|
+
width: 440px;
|
|
213
|
+
height: 248px;
|
|
214
|
+
box-sizing: border-box;
|
|
215
|
+
overflow: hidden;
|
|
216
|
+
border: 1px solid rgba(255, 195, 122, 0.75);
|
|
217
|
+
background: transparent;
|
|
218
|
+
box-shadow: 0 24px 90px rgba(0, 0, 0, 0.45);
|
|
219
|
+
}
|
|
220
|
+
#detail-tint {
|
|
221
|
+
position: absolute;
|
|
222
|
+
inset: 0;
|
|
223
|
+
background: linear-gradient(0deg, rgba(4, 8, 11, 0.32) 0%, rgba(4, 8, 11, 0) 45%);
|
|
224
|
+
pointer-events: none;
|
|
225
|
+
}
|
|
226
|
+
#footer {
|
|
227
|
+
position: absolute;
|
|
228
|
+
left: 148px;
|
|
229
|
+
right: 148px;
|
|
230
|
+
bottom: 70px;
|
|
231
|
+
contain: layout paint style;
|
|
232
|
+
animation: fade-in 1s linear both;
|
|
233
|
+
}
|
|
234
|
+
#rule {
|
|
235
|
+
position: relative;
|
|
236
|
+
height: 1px;
|
|
237
|
+
background: rgba(238, 244, 241, 0.24);
|
|
238
|
+
transform-origin: left center;
|
|
239
|
+
animation: draw 1s cubic-bezier(0.215, 0.61, 0.355, 1) both;
|
|
240
|
+
}
|
|
241
|
+
#progress {
|
|
242
|
+
position: absolute;
|
|
243
|
+
inset: 0 auto 0 0;
|
|
244
|
+
width: calc(var(--fy-progress) * 100%);
|
|
245
|
+
background: var(--fy-prop-accent);
|
|
246
|
+
box-shadow: 0 0 14px var(--fy-prop-accent);
|
|
247
|
+
}
|
|
248
|
+
#footer-row {
|
|
249
|
+
display: flex;
|
|
250
|
+
justify-content: space-between;
|
|
251
|
+
align-items: baseline;
|
|
252
|
+
gap: 24px;
|
|
253
|
+
margin-top: 18px;
|
|
254
|
+
font:
|
|
255
|
+
500 12px/1 ui-monospace,
|
|
256
|
+
monospace;
|
|
257
|
+
letter-spacing: 0.14em;
|
|
258
|
+
color: #a4b1b3;
|
|
259
|
+
text-transform: uppercase;
|
|
260
|
+
text-shadow: 0 2px 14px rgba(0, 0, 0, 0.7);
|
|
261
|
+
}
|
|
262
|
+
#footer-row strong {
|
|
263
|
+
color: #edf5f2;
|
|
264
|
+
font-weight: 600;
|
|
265
|
+
}
|
|
266
|
+
#footer-row .accent {
|
|
267
|
+
color: var(--fy-prop-accent);
|
|
268
|
+
}
|
|
269
|
+
#frame-counter {
|
|
270
|
+
display: inline-block;
|
|
271
|
+
min-width: 3ch;
|
|
272
|
+
text-align: right;
|
|
273
|
+
}
|
|
274
|
+
#frame-counter::after {
|
|
275
|
+
counter-reset: fy-frame var(--fy-frame);
|
|
276
|
+
content: counter(fy-frame);
|
|
277
|
+
}
|
|
278
|
+
#outro-fade {
|
|
279
|
+
position: absolute;
|
|
280
|
+
inset: 0;
|
|
281
|
+
background: #04080b;
|
|
282
|
+
pointer-events: none;
|
|
283
|
+
opacity: 0;
|
|
284
|
+
animation: outro-fade 1s linear both;
|
|
285
|
+
}
|
|
286
|
+
</style>
|
|
287
|
+
</head>
|
|
288
|
+
<body>
|
|
289
|
+
<fy-composition width="1920" height="1080" fps="30" duration-in-frames="360">
|
|
290
|
+
<div id="scene">
|
|
291
|
+
<div id="scrim"></div>
|
|
292
|
+
<div id="footer-scrim"></div>
|
|
293
|
+
<div id="frame-border"></div>
|
|
294
|
+
|
|
295
|
+
<header id="header" data-fy-anim-start="8" data-fy-anim-duration="28">
|
|
296
|
+
<div id="kicker" data-fy-text="kicker"></div>
|
|
297
|
+
<div id="format">Timeline video / <span data-fy-text="format"></span></div>
|
|
298
|
+
</header>
|
|
299
|
+
|
|
300
|
+
<div id="copy">
|
|
301
|
+
<h1 id="title">
|
|
302
|
+
<span
|
|
303
|
+
data-fy-text="title"
|
|
304
|
+
data-fy-anim-start="14"
|
|
305
|
+
data-fy-anim-duration="44"
|
|
306
|
+
data-fy-anim-easing="cubic-bezier(0.215, 0.61, 0.355, 1)"
|
|
307
|
+
></span>
|
|
308
|
+
<em
|
|
309
|
+
data-fy-text="titleAccent"
|
|
310
|
+
data-fy-anim-start="26"
|
|
311
|
+
data-fy-anim-duration="44"
|
|
312
|
+
data-fy-anim-easing="cubic-bezier(0.215, 0.61, 0.355, 1)"
|
|
313
|
+
></em>
|
|
314
|
+
</h1>
|
|
315
|
+
<p
|
|
316
|
+
id="subtitle"
|
|
317
|
+
data-fy-text="subtitle"
|
|
318
|
+
data-fy-anim-start="40"
|
|
319
|
+
data-fy-anim-duration="40"
|
|
320
|
+
data-fy-anim-easing="cubic-bezier(0.215, 0.61, 0.355, 1)"
|
|
321
|
+
></p>
|
|
322
|
+
</div>
|
|
323
|
+
|
|
324
|
+
<div id="detail" data-fy-anim-start="112" data-fy-anim-duration="170">
|
|
325
|
+
<div id="detail-bar">
|
|
326
|
+
<span data-fy-text="detail"></span>
|
|
327
|
+
<span id="detail-time">SRC 5.8–10.8 S</span>
|
|
328
|
+
</div>
|
|
329
|
+
<div id="detail-window">
|
|
330
|
+
<div id="detail-tint"></div>
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
<div id="footer" data-fy-anim-start="46" data-fy-anim-duration="30">
|
|
335
|
+
<div id="rule" data-fy-anim-start="52" data-fy-anim-duration="50">
|
|
336
|
+
<div id="progress"></div>
|
|
337
|
+
</div>
|
|
338
|
+
<div id="footer-row">
|
|
339
|
+
<span>Hero / v1 · SRC <strong>0.0–12.0 S</strong></span>
|
|
340
|
+
<span>Detail / v2 · SRC <strong>5.8–10.8 S</strong></span>
|
|
341
|
+
<span>Frame <strong id="frame-counter"></strong> / 359</span>
|
|
342
|
+
<span class="accent">HTML overlay / v3 · 30 FPS</span>
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
|
|
346
|
+
<div id="outro-fade" data-fy-anim-start="329" data-fy-anim-duration="30"></div>
|
|
347
|
+
</div>
|
|
348
|
+
</fy-composition>
|
|
349
|
+
</body>
|
|
350
|
+
</html>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"name": "Video on a track, HTML overlay on top (MP4)",
|
|
4
|
+
"tracks": [
|
|
5
|
+
{ "id": "v1", "kind": "video", "name": "Hero video" },
|
|
6
|
+
{ "id": "v2", "kind": "video", "name": "Detail video" },
|
|
7
|
+
{ "id": "v3", "kind": "video", "name": "HTML overlay" }
|
|
8
|
+
],
|
|
9
|
+
"clips": [
|
|
10
|
+
{
|
|
11
|
+
"id": "hero",
|
|
12
|
+
"track": "v1",
|
|
13
|
+
"source": { "kind": "video", "src": "assets/clip.mp4" },
|
|
14
|
+
"start": 0,
|
|
15
|
+
"audio": { "gain": 0.85 }
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "detail",
|
|
19
|
+
"track": "v2",
|
|
20
|
+
"source": { "kind": "video", "src": "assets/clip.mp4" },
|
|
21
|
+
"start": 120,
|
|
22
|
+
"sourceIn": 174,
|
|
23
|
+
"duration": 150,
|
|
24
|
+
"transform": {
|
|
25
|
+
"fit": "cover",
|
|
26
|
+
"width": 440,
|
|
27
|
+
"height": 248,
|
|
28
|
+
"x": 592,
|
|
29
|
+
"y": 262,
|
|
30
|
+
"rotation": 0,
|
|
31
|
+
"opacity": 1
|
|
32
|
+
},
|
|
33
|
+
"audio": { "muted": true }
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "overlay",
|
|
37
|
+
"track": "v3",
|
|
38
|
+
"source": { "kind": "composition", "id": "video-overlay" },
|
|
39
|
+
"start": 0,
|
|
40
|
+
"duration": 360,
|
|
41
|
+
"props": { "format": "H.264 + AAC / 30 FPS" }
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"name": "Video on a track, HTML overlay on top",
|
|
4
|
+
"tracks": [
|
|
5
|
+
{ "id": "v1", "kind": "video", "name": "Hero video" },
|
|
6
|
+
{ "id": "v2", "kind": "video", "name": "Detail video" },
|
|
7
|
+
{ "id": "v3", "kind": "video", "name": "HTML overlay" }
|
|
8
|
+
],
|
|
9
|
+
"clips": [
|
|
10
|
+
{
|
|
11
|
+
"id": "hero",
|
|
12
|
+
"track": "v1",
|
|
13
|
+
"source": { "kind": "video", "src": "assets/clip.webm" },
|
|
14
|
+
"start": 0,
|
|
15
|
+
"audio": { "gain": 0.85 }
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "detail",
|
|
19
|
+
"track": "v2",
|
|
20
|
+
"source": { "kind": "video", "src": "assets/clip.webm" },
|
|
21
|
+
"start": 120,
|
|
22
|
+
"sourceIn": 174,
|
|
23
|
+
"duration": 150,
|
|
24
|
+
"transform": {
|
|
25
|
+
"fit": "cover",
|
|
26
|
+
"width": 440,
|
|
27
|
+
"height": 248,
|
|
28
|
+
"x": 592,
|
|
29
|
+
"y": 262,
|
|
30
|
+
"rotation": 0,
|
|
31
|
+
"opacity": 1
|
|
32
|
+
},
|
|
33
|
+
"audio": { "muted": true }
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "overlay",
|
|
37
|
+
"track": "v3",
|
|
38
|
+
"source": { "kind": "composition", "id": "video-overlay" },
|
|
39
|
+
"start": 0,
|
|
40
|
+
"duration": 360,
|
|
41
|
+
"props": { "format": "VP9 + Opus / 30 FPS" }
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export default {
|
|
2
|
+
format: { width: 1920, height: 1080, fps: 30 },
|
|
2
3
|
compositions: {
|
|
3
4
|
video: "./compositions/video.html",
|
|
4
5
|
"video-mp4": "./compositions/video-mp4.html",
|
|
6
|
+
"video-overlay": "./compositions/video-overlay.html",
|
|
7
|
+
},
|
|
8
|
+
timelines: {
|
|
9
|
+
"video-timeline": "./timelines/video-timeline.timeline.json",
|
|
10
|
+
"video-timeline-mp4": "./timelines/video-timeline-mp4.timeline.json",
|
|
5
11
|
},
|
|
6
12
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frameyard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Local-first, deterministic video composition framework for HTML, CSS, and JavaScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"@types/node": "26.6.5",
|
|
60
60
|
"oxfmt": "0.75.0",
|
|
61
61
|
"oxlint": "1.90.0",
|
|
62
|
-
"typescript": "
|
|
62
|
+
"typescript": "7.0.2",
|
|
63
63
|
"vitest": "5.0.5"
|
|
64
64
|
},
|
|
65
65
|
"scripts": {
|
|
@@ -74,8 +74,10 @@
|
|
|
74
74
|
"check:static": "pnpm check:scripts && pnpm lint && pnpm format:check && pnpm typecheck && pnpm typecheck:test && pnpm typecheck:browser",
|
|
75
75
|
"compare-stills": "node scripts/compare-stills.mjs",
|
|
76
76
|
"check": "pnpm check:static && pnpm test",
|
|
77
|
+
"check:preview": "pnpm check:static && pnpm test:preview",
|
|
77
78
|
"test": "pnpm build && vitest run",
|
|
78
|
-
"test:
|
|
79
|
+
"test:preview": "pnpm build && vitest run test/preview-runtime.test.ts test/timeline-preview.test.ts test/timeline-preview-runtime.test.ts",
|
|
80
|
+
"test:unit": "vitest run test/contracts.test.ts test/docs.test.ts test/encoders.test.ts test/random.test.ts test/release.test.ts test/scaffold.test.ts test/transcript.test.ts",
|
|
79
81
|
"bench": "pnpm build && node scripts/bench-render.mjs",
|
|
80
82
|
"set-version": "node scripts/set-version.mjs",
|
|
81
83
|
"verify-release": "node scripts/verify-release.mjs",
|
|
@@ -10,16 +10,18 @@ Install from the repository with the open agent-skills CLI:
|
|
|
10
10
|
npx skills add vladiantio/frameyard
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
The skill lives at `skills/frameyard` in the repository and npm package. Alternatively, copy or symlink the whole `skills/frameyard/` directory into the skill location your agent host uses
|
|
13
|
+
The skill lives at `skills/frameyard` in the repository and npm package. Alternatively, copy or symlink the whole `skills/frameyard/` directory into the skill location your agent host uses:
|
|
14
14
|
|
|
15
|
-
- Claude Code
|
|
16
|
-
-
|
|
17
|
-
-
|
|
15
|
+
- Project-scoped: `.claude/skills/frameyard` for Claude Code, or `.agents/skills/frameyard` for hosts that read the shared `.agents` container.
|
|
16
|
+
- Personal or global: `~/.claude/skills/frameyard`, `~/.agents/skills/frameyard`, or another host's personal directory; for example, OpenCode reads `~/.config/opencode/skills/frameyard`.
|
|
17
|
+
- Keep one canonical copy and symlink it into the host directories you use, so updates do not drift.
|
|
18
18
|
|
|
19
|
-
The skill is host-neutral: it uses only the normal `frameyard` project format and CLI. There is no agent-only runtime.
|
|
19
|
+
The skill is host-neutral: it uses only the normal `frameyard` project format and CLI. There is no agent-only runtime. All `references/`, `scripts/`, and `SKILL.md` paths inside the skill are relative, so the directory works wherever it is installed.
|
|
20
20
|
|
|
21
21
|
Run the CLI with `npx frameyard`. It resolves the latest published version; pin it with `npx frameyard@<version>` or install the package locally when the CLI version must match the skill. When the skill is copied outside the npm package, `scripts/frameyard.mjs` falls back to the `frameyard` command on `PATH`; if that command is unavailable, set `FRAMEYARD_CLI` to an installed CLI path.
|
|
22
22
|
|
|
23
|
+
When you work inside the Frameyard repository itself, `npx frameyard` resolves the published package, not your working tree. Run the built CLI (`node dist/cli.js`, after `pnpm build`) or `skills/frameyard/scripts/frameyard.mjs`, which prefers the repository build. The repository `AGENTS.md` is the authority for development commands.
|
|
24
|
+
|
|
23
25
|
## Contents
|
|
24
26
|
|
|
25
27
|
- `SKILL.md`: trigger description and operating workflow
|