frameyard 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/GLOSSARY.md +156 -24
  3. package/README.md +103 -31
  4. package/dist/cli.js +112 -19
  5. package/dist/cli.js.map +1 -1
  6. package/dist/composition/adapter.d.ts +30 -0
  7. package/dist/composition/adapter.d.ts.map +1 -0
  8. package/dist/composition/adapter.js +2 -0
  9. package/dist/composition/adapter.js.map +1 -0
  10. package/dist/composition/animation.d.ts +19 -0
  11. package/dist/composition/animation.d.ts.map +1 -0
  12. package/dist/composition/animation.js +55 -0
  13. package/dist/composition/animation.js.map +1 -0
  14. package/dist/composition/attributes.d.ts +7 -0
  15. package/dist/composition/attributes.d.ts.map +1 -0
  16. package/dist/composition/attributes.js +24 -0
  17. package/dist/composition/attributes.js.map +1 -0
  18. package/dist/composition/descriptor.d.ts +30 -0
  19. package/dist/composition/descriptor.d.ts.map +1 -0
  20. package/dist/composition/descriptor.js +188 -0
  21. package/dist/composition/descriptor.js.map +1 -0
  22. package/dist/composition/diagnostics.d.ts +12 -0
  23. package/dist/composition/diagnostics.d.ts.map +1 -0
  24. package/dist/composition/diagnostics.js +38 -0
  25. package/dist/composition/diagnostics.js.map +1 -0
  26. package/dist/composition/index.d.ts +12 -0
  27. package/dist/composition/index.d.ts.map +1 -0
  28. package/dist/composition/index.js +7 -0
  29. package/dist/composition/index.js.map +1 -0
  30. package/dist/composition/media.d.ts +27 -0
  31. package/dist/composition/media.d.ts.map +1 -0
  32. package/dist/composition/media.js +162 -0
  33. package/dist/composition/media.js.map +1 -0
  34. package/dist/composition/paths.d.ts +5 -0
  35. package/dist/composition/paths.d.ts.map +1 -0
  36. package/dist/composition/paths.js +16 -0
  37. package/dist/composition/paths.js.map +1 -0
  38. package/dist/composition/transcripts.d.ts +19 -0
  39. package/dist/composition/transcripts.d.ts.map +1 -0
  40. package/dist/composition/transcripts.js +57 -0
  41. package/dist/composition/transcripts.js.map +1 -0
  42. package/dist/composition/walk.d.ts +10 -0
  43. package/dist/composition/walk.d.ts.map +1 -0
  44. package/dist/composition/walk.js +32 -0
  45. package/dist/composition/walk.js.map +1 -0
  46. package/dist/contracts.d.ts +14 -0
  47. package/dist/contracts.d.ts.map +1 -1
  48. package/dist/contracts.js.map +1 -1
  49. package/dist/errors.d.ts +1 -1
  50. package/dist/errors.d.ts.map +1 -1
  51. package/dist/errors.js.map +1 -1
  52. package/dist/html/document-adapter.d.ts +5 -0
  53. package/dist/html/document-adapter.d.ts.map +1 -0
  54. package/dist/html/document-adapter.js +17 -0
  55. package/dist/html/document-adapter.js.map +1 -0
  56. package/dist/html/parse.d.ts +2 -6
  57. package/dist/html/parse.d.ts.map +1 -1
  58. package/dist/html/parse.js.map +1 -1
  59. package/dist/html/validate.d.ts.map +1 -1
  60. package/dist/html/validate.js +92 -375
  61. package/dist/html/validate.js.map +1 -1
  62. package/dist/index.d.ts +2 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +1 -0
  65. package/dist/index.js.map +1 -1
  66. package/dist/preview/main.d.ts.map +1 -1
  67. package/dist/preview/main.js +93 -13
  68. package/dist/preview/main.js.map +1 -1
  69. package/dist/preview/timeline-data.d.ts +85 -0
  70. package/dist/preview/timeline-data.d.ts.map +1 -0
  71. package/dist/preview/timeline-data.js +2 -0
  72. package/dist/preview/timeline-data.js.map +1 -0
  73. package/dist/preview/timeline-host.d.ts +19 -0
  74. package/dist/preview/timeline-host.d.ts.map +1 -0
  75. package/dist/preview/timeline-host.js +297 -0
  76. package/dist/preview/timeline-host.js.map +1 -0
  77. package/dist/preview/timeline-main.d.ts +17 -0
  78. package/dist/preview/timeline-main.d.ts.map +1 -0
  79. package/dist/preview/timeline-main.js +364 -0
  80. package/dist/preview/timeline-main.js.map +1 -0
  81. package/dist/project.d.ts +16 -1
  82. package/dist/project.d.ts.map +1 -1
  83. package/dist/project.js +81 -26
  84. package/dist/project.js.map +1 -1
  85. package/dist/renderer/browser.d.ts +2 -0
  86. package/dist/renderer/browser.d.ts.map +1 -1
  87. package/dist/renderer/browser.js +38 -6
  88. package/dist/renderer/browser.js.map +1 -1
  89. package/dist/renderer/ffmpeg.d.ts +61 -0
  90. package/dist/renderer/ffmpeg.d.ts.map +1 -1
  91. package/dist/renderer/ffmpeg.js +198 -0
  92. package/dist/renderer/ffmpeg.js.map +1 -1
  93. package/dist/renderer/index.d.ts.map +1 -1
  94. package/dist/renderer/index.js +18 -7
  95. package/dist/renderer/index.js.map +1 -1
  96. package/dist/renderer/timeline.d.ts +109 -0
  97. package/dist/renderer/timeline.d.ts.map +1 -0
  98. package/dist/renderer/timeline.js +984 -0
  99. package/dist/renderer/timeline.js.map +1 -0
  100. package/dist/runtime/document-adapter.d.ts +4 -0
  101. package/dist/runtime/document-adapter.d.ts.map +1 -0
  102. package/dist/runtime/document-adapter.js +19 -0
  103. package/dist/runtime/document-adapter.js.map +1 -0
  104. package/dist/runtime/host.d.ts +2 -0
  105. package/dist/runtime/host.d.ts.map +1 -1
  106. package/dist/runtime/host.js +78 -183
  107. package/dist/runtime/host.js.map +1 -1
  108. package/dist/runtime/media.d.ts.map +1 -1
  109. package/dist/runtime/media.js +19 -50
  110. package/dist/runtime/media.js.map +1 -1
  111. package/dist/scaffold.d.ts.map +1 -1
  112. package/dist/scaffold.js +93 -23
  113. package/dist/scaffold.js.map +1 -1
  114. package/dist/server/preview-html.d.ts.map +1 -1
  115. package/dist/server/preview-html.js +15 -1
  116. package/dist/server/preview-html.js.map +1 -1
  117. package/dist/server/server.d.ts.map +1 -1
  118. package/dist/server/server.js +39 -1
  119. package/dist/server/server.js.map +1 -1
  120. package/dist/server/timeline-preview-data.d.ts +4 -0
  121. package/dist/server/timeline-preview-data.d.ts.map +1 -0
  122. package/dist/server/timeline-preview-data.js +97 -0
  123. package/dist/server/timeline-preview-data.js.map +1 -0
  124. package/dist/server/timeline-preview-html.d.ts +4 -0
  125. package/dist/server/timeline-preview-html.d.ts.map +1 -0
  126. package/dist/server/timeline-preview-html.js +227 -0
  127. package/dist/server/timeline-preview-html.js.map +1 -0
  128. package/dist/timeline/plan.d.ts +32 -0
  129. package/dist/timeline/plan.d.ts.map +1 -0
  130. package/dist/timeline/plan.js +68 -0
  131. package/dist/timeline/plan.js.map +1 -0
  132. package/dist/timeline/resolve.d.ts +60 -0
  133. package/dist/timeline/resolve.d.ts.map +1 -0
  134. package/dist/timeline/resolve.js +523 -0
  135. package/dist/timeline/resolve.js.map +1 -0
  136. package/dist/timeline/schema.d.ts +71 -0
  137. package/dist/timeline/schema.d.ts.map +1 -0
  138. package/dist/timeline/schema.js +425 -0
  139. package/dist/timeline/schema.js.map +1 -0
  140. package/dist/version.d.ts +1 -1
  141. package/dist/version.js +1 -1
  142. package/docs/adr/0002-make-frame-and-props-the-only-animation-state.md +2 -0
  143. package/docs/adr/0005-keep-parallel-rendering-opt-in-and-frame-ordered.md +2 -0
  144. package/docs/adr/0006-seek-embedded-media-from-the-frame-clock.md +4 -0
  145. package/docs/adr/0007-make-html-documents-the-composition-unit.md +1 -1
  146. package/docs/adr/0008-share-the-composition-document-grammar.md +18 -0
  147. package/docs/adr/0009-composite-production-frames-in-software.md +37 -0
  148. package/docs/adr/0010-make-timelines-the-render-unit.md +24 -0
  149. package/docs/adr/0011-compose-timeline-frames-in-a-live-browser-host.md +22 -0
  150. package/docs/adr/0012-render-timelines-with-segmented-lossless-clip-intermediates.md +31 -0
  151. package/docs/v0.1-design.md +135 -85
  152. package/examples/demo/README.md +11 -4
  153. package/examples/demo/timelines/demo.timeline.json +12 -0
  154. package/examples/demo/video.config.js +4 -0
  155. package/examples/video/README.md +105 -0
  156. package/examples/video/assets/clip.mp4 +0 -0
  157. package/examples/video/assets/clip.webm +0 -0
  158. package/examples/video/compositions/video-mp4.html +362 -0
  159. package/examples/video/compositions/video-overlay.html +350 -0
  160. package/examples/video/compositions/video.html +362 -0
  161. package/examples/video/props/video-timeline.json +10 -0
  162. package/examples/video/props/video.json +8 -0
  163. package/examples/video/scripts/make-clip.sh +109 -0
  164. package/examples/video/timelines/video-timeline-mp4.timeline.json +44 -0
  165. package/examples/video/timelines/video-timeline.timeline.json +44 -0
  166. package/examples/video/video.config.js +12 -0
  167. package/examples/webgl/README.md +30 -0
  168. package/examples/webgl/compositions/webgl.controller.js +200 -0
  169. package/examples/webgl/compositions/webgl.html +215 -0
  170. package/examples/webgl/props/webgl.json +6 -0
  171. package/examples/webgl/video.config.js +5 -0
  172. package/package.json +4 -2
  173. package/skills/frameyard/SKILL.md +18 -13
  174. package/skills/frameyard/references/json-results.md +2 -2
  175. package/skills/frameyard/references/timelines.md +141 -0
@@ -0,0 +1,105 @@
1
+ # Frameyard video example
2
+
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
+
6
+ - `video` reads `assets/clip.webm` (VP9 + Opus)
7
+ - `video-mp4` reads `assets/clip.mp4` (H.264 + AAC)
8
+
9
+ Both compositions include:
10
+
11
+ - a full-bleed hero pass whose unmuted soundtrack joins the composition audio timeline
12
+ - a persistent detail pass with its own `data-fy-start`, `data-fy-trim`, and `data-fy-duration`
13
+ - `data-fy-audio="off"` on the detail pass so only the hero contributes audio
14
+ - a frame counter derived from `--fy-frame` in CSS, with static source-range labels
15
+ - frame-scheduled CSS typography and a frame-state progress bar
16
+ - a one-second fade to black over the final second of the composition
17
+
18
+ The hero pass uses the full 12-second source at volume `0.85`. The detail window
19
+ is visible for the whole composition: it holds the source at 5.8 seconds until
20
+ composition frame 120, then plays 5 seconds of the same file, and holds the final
21
+ source frame after frame 270. The result shows two source moments side by side.
22
+ The example is entirely declarative: the runtime owns loading, pausing, and
23
+ frame seeking, and CSS derives the frame counter from the frame state. No
24
+ controller is needed. The header, copy, and footer fade individually rather
25
+ than the whole scene; a full-scene opacity animation above a video can leave
26
+ the load frame on screen in interactive preview after a large seek.
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
+
63
+ The compositions live in `compositions/video.html` and
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.
71
+
72
+ Regenerate both clips from scratch with the bundled FFmpeg script:
73
+
74
+ ```sh
75
+ bash examples/video/scripts/make-clip.sh
76
+ ```
77
+
78
+ The script writes both files into `assets/`; pass `--out-dir <directory>` to
79
+ write elsewhere. It requires FFmpeg with `libvpx-vp9`, `libx264`, Opus, and AAC
80
+ encoders, and fixed noise seeds keep the generated grain stable across runs on
81
+ the same FFmpeg build.
82
+
83
+ From the repository root:
84
+
85
+ ```sh
86
+ pnpm build
87
+ node dist/cli.js check video --project examples/video
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
91
+ node dist/cli.js still video --frame 210 --project examples/video --out video-still.png
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
95
+ node dist/cli.js render video --project examples/video --props examples/video/props/video.json
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
99
+ ```
100
+
101
+ Start the interactive preview with:
102
+
103
+ ```sh
104
+ node dist/cli.js dev --project examples/video
105
+ ```
Binary file
Binary file
@@ -0,0 +1,362 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <script type="application/json" data-fy="props">
6
+ {
7
+ "kicker": "Frameyard / Clip Study 001",
8
+ "title": "Moving",
9
+ "titleAccent": "Image",
10
+ "subtitle": "A local clip, loaded once and seeked twice from the composition clock.",
11
+ "detail": "Detail / second pass",
12
+ "accent": "#ffc37a"
13
+ }
14
+ </script>
15
+ <style>
16
+ @keyframes fade-in {
17
+ from {
18
+ opacity: 0;
19
+ }
20
+ to {
21
+ opacity: 1;
22
+ }
23
+ }
24
+ @keyframes rise {
25
+ from {
26
+ opacity: 0;
27
+ transform: translateY(42px);
28
+ }
29
+ to {
30
+ opacity: 1;
31
+ transform: translateY(0);
32
+ }
33
+ }
34
+ @keyframes draw {
35
+ from {
36
+ opacity: 0;
37
+ transform: scaleX(0);
38
+ }
39
+ to {
40
+ opacity: 1;
41
+ transform: scaleX(1);
42
+ }
43
+ }
44
+ @keyframes outro-fade {
45
+ from {
46
+ opacity: 0;
47
+ }
48
+ to {
49
+ opacity: 1;
50
+ }
51
+ }
52
+ html,
53
+ body,
54
+ fy-composition {
55
+ background: #04080b;
56
+ }
57
+ #scene {
58
+ position: relative;
59
+ width: 100%;
60
+ height: 100%;
61
+ overflow: hidden;
62
+ isolation: isolate;
63
+ background: #04080b;
64
+ color: #eef4f1;
65
+ font-family: Inter, "Helvetica Neue", ui-sans-serif, system-ui, sans-serif;
66
+ }
67
+ #hero {
68
+ position: absolute;
69
+ inset: 0;
70
+ display: block;
71
+ width: 100%;
72
+ height: 100%;
73
+ object-fit: cover;
74
+ transform: scale(calc(1 + var(--fy-progress) * 0.035));
75
+ transform-origin: center;
76
+ }
77
+ #scrim {
78
+ position: absolute;
79
+ inset: 0;
80
+ background:
81
+ linear-gradient(
82
+ 90deg,
83
+ rgba(4, 8, 11, 0.94) 0%,
84
+ rgba(4, 8, 11, 0.8) 24%,
85
+ rgba(4, 8, 11, 0.24) 52%,
86
+ rgba(4, 8, 11, 0) 74%
87
+ ),
88
+ linear-gradient(0deg, rgba(4, 8, 11, 0.9) 0%, rgba(4, 8, 11, 0) 34%);
89
+ }
90
+ #vignette {
91
+ position: absolute;
92
+ inset: 0;
93
+ background: radial-gradient(ellipse at 62% 44%, transparent 42%, rgba(0, 0, 0, 0.3) 100%);
94
+ pointer-events: none;
95
+ }
96
+ #frame-border {
97
+ position: absolute;
98
+ inset: 36px;
99
+ border: 1px solid rgba(238, 244, 241, 0.16);
100
+ pointer-events: none;
101
+ }
102
+ #header {
103
+ position: absolute;
104
+ top: 80px;
105
+ left: 148px;
106
+ right: 148px;
107
+ display: flex;
108
+ justify-content: space-between;
109
+ align-items: center;
110
+ gap: 32px;
111
+ animation: fade-in 1s linear both;
112
+ }
113
+ #kicker {
114
+ display: flex;
115
+ align-items: center;
116
+ gap: 15px;
117
+ font:
118
+ 600 14px/1 ui-monospace,
119
+ monospace;
120
+ letter-spacing: 0.24em;
121
+ text-transform: uppercase;
122
+ color: var(--fy-prop-accent);
123
+ }
124
+ #kicker::before {
125
+ content: "";
126
+ width: 8px;
127
+ height: 8px;
128
+ background: var(--fy-prop-accent);
129
+ box-shadow: 0 0 16px var(--fy-prop-accent);
130
+ }
131
+ #format {
132
+ font:
133
+ 500 12px/1 ui-monospace,
134
+ monospace;
135
+ letter-spacing: 0.16em;
136
+ text-transform: uppercase;
137
+ color: #8b999c;
138
+ }
139
+ #copy {
140
+ position: absolute;
141
+ top: 278px;
142
+ left: 148px;
143
+ width: 860px;
144
+ }
145
+ #title {
146
+ margin: 0;
147
+ font-size: 174px;
148
+ line-height: 0.84;
149
+ letter-spacing: -0.064em;
150
+ font-weight: 660;
151
+ }
152
+ #title span,
153
+ #title em {
154
+ display: block;
155
+ animation: rise 1s cubic-bezier(0.215, 0.61, 0.355, 1) both;
156
+ }
157
+ #title em {
158
+ margin-top: 0.06em;
159
+ font-family: "Iowan Old Style", "Palatino Linotype", Georgia, ui-serif, serif;
160
+ font-style: italic;
161
+ font-weight: 400;
162
+ letter-spacing: -0.035em;
163
+ color: var(--fy-prop-accent);
164
+ }
165
+ #subtitle {
166
+ width: 720px;
167
+ margin: 38px 0 0;
168
+ font-size: 29px;
169
+ line-height: 1.5;
170
+ font-weight: 420;
171
+ color: #a6b3b6;
172
+ animation: rise 1s cubic-bezier(0.215, 0.61, 0.355, 1) both;
173
+ }
174
+ #footer {
175
+ position: absolute;
176
+ left: 148px;
177
+ right: 148px;
178
+ bottom: 70px;
179
+ contain: layout paint style;
180
+ animation: fade-in 1s linear both;
181
+ }
182
+ #rule {
183
+ position: relative;
184
+ height: 1px;
185
+ background: rgba(238, 244, 241, 0.16);
186
+ transform-origin: left center;
187
+ animation: draw 1s cubic-bezier(0.215, 0.61, 0.355, 1) both;
188
+ }
189
+ #progress {
190
+ position: absolute;
191
+ inset: 0 auto 0 0;
192
+ width: calc(var(--fy-progress) * 100%);
193
+ background: var(--fy-prop-accent);
194
+ box-shadow: 0 0 14px var(--fy-prop-accent);
195
+ }
196
+ #footer-row {
197
+ display: flex;
198
+ justify-content: space-between;
199
+ align-items: baseline;
200
+ gap: 24px;
201
+ margin-top: 18px;
202
+ font:
203
+ 500 12px/1 ui-monospace,
204
+ monospace;
205
+ letter-spacing: 0.14em;
206
+ color: #879598;
207
+ text-transform: uppercase;
208
+ }
209
+ #footer-row strong {
210
+ color: #edf5f2;
211
+ font-weight: 600;
212
+ }
213
+ #footer-row .accent {
214
+ color: var(--fy-prop-accent);
215
+ }
216
+ #frame-counter {
217
+ display: inline-block;
218
+ min-width: 3ch;
219
+ text-align: right;
220
+ }
221
+ #frame-counter::after {
222
+ counter-reset: fy-frame var(--fy-frame);
223
+ content: counter(fy-frame);
224
+ }
225
+ #detail {
226
+ position: absolute;
227
+ right: 148px;
228
+ bottom: 154px;
229
+ width: 440px;
230
+ }
231
+ #detail-bar {
232
+ display: flex;
233
+ justify-content: space-between;
234
+ align-items: baseline;
235
+ gap: 24px;
236
+ margin-bottom: 10px;
237
+ contain: layout paint style;
238
+ font:
239
+ 600 11px/1 ui-monospace,
240
+ monospace;
241
+ letter-spacing: 0.18em;
242
+ text-transform: uppercase;
243
+ }
244
+ #detail-bar > span:first-child {
245
+ color: var(--fy-prop-accent);
246
+ }
247
+ #detail-time {
248
+ color: #98a5a6;
249
+ font-weight: 500;
250
+ }
251
+ #detail-window {
252
+ position: relative;
253
+ width: 440px;
254
+ height: 248px;
255
+ overflow: hidden;
256
+ border: 1px solid rgba(255, 195, 122, 0.65);
257
+ background: #000;
258
+ box-shadow: 0 24px 90px rgba(0, 0, 0, 0.55);
259
+ }
260
+ #detail-clip {
261
+ display: block;
262
+ width: 100%;
263
+ height: 100%;
264
+ object-fit: cover;
265
+ object-position: 68% 50%;
266
+ transform: scale(1.3);
267
+ }
268
+ #detail-tint {
269
+ position: absolute;
270
+ inset: 0;
271
+ background: linear-gradient(0deg, rgba(4, 8, 11, 0.28) 0%, rgba(4, 8, 11, 0) 45%);
272
+ pointer-events: none;
273
+ }
274
+ #outro-fade {
275
+ position: absolute;
276
+ inset: 0;
277
+ background: #04080b;
278
+ pointer-events: none;
279
+ opacity: 0;
280
+ animation: outro-fade 1s linear both;
281
+ }
282
+ </style>
283
+ </head>
284
+ <body>
285
+ <fy-composition width="1920" height="1080" fps="30" duration-in-frames="360">
286
+ <div id="scene">
287
+ <video
288
+ id="hero"
289
+ src="assets/clip.mp4"
290
+ data-fy-start="0"
291
+ data-fy-trim="0"
292
+ data-fy-duration="12"
293
+ data-fy-volume="0.85"
294
+ ></video>
295
+ <div id="scrim"></div>
296
+ <div id="vignette"></div>
297
+ <div id="frame-border"></div>
298
+
299
+ <header id="header" data-fy-anim-start="8" data-fy-anim-duration="28">
300
+ <div id="kicker" data-fy-text="kicker"></div>
301
+ <div id="format">Embedded video / H.264 + AAC / 30 FPS</div>
302
+ </header>
303
+
304
+ <div id="copy">
305
+ <h1 id="title">
306
+ <span
307
+ data-fy-text="title"
308
+ data-fy-anim-start="14"
309
+ data-fy-anim-duration="44"
310
+ data-fy-anim-easing="cubic-bezier(0.215, 0.61, 0.355, 1)"
311
+ ></span>
312
+ <em
313
+ data-fy-text="titleAccent"
314
+ data-fy-anim-start="26"
315
+ data-fy-anim-duration="44"
316
+ data-fy-anim-easing="cubic-bezier(0.215, 0.61, 0.355, 1)"
317
+ ></em>
318
+ </h1>
319
+ <p
320
+ id="subtitle"
321
+ data-fy-text="subtitle"
322
+ data-fy-anim-start="40"
323
+ data-fy-anim-duration="40"
324
+ data-fy-anim-easing="cubic-bezier(0.215, 0.61, 0.355, 1)"
325
+ ></p>
326
+ </div>
327
+
328
+ <div id="detail">
329
+ <div id="detail-bar">
330
+ <span data-fy-text="detail"></span>
331
+ <span id="detail-time">SRC 5.8–10.8 S</span>
332
+ </div>
333
+ <div id="detail-window">
334
+ <video
335
+ id="detail-clip"
336
+ src="assets/clip.mp4"
337
+ data-fy-start="120"
338
+ data-fy-trim="5.8"
339
+ data-fy-duration="5"
340
+ data-fy-audio="off"
341
+ ></video>
342
+ <div id="detail-tint"></div>
343
+ </div>
344
+ </div>
345
+
346
+ <div id="footer" data-fy-anim-start="46" data-fy-anim-duration="30">
347
+ <div id="rule" data-fy-anim-start="52" data-fy-anim-duration="50">
348
+ <div id="progress"></div>
349
+ </div>
350
+ <div id="footer-row">
351
+ <span>Hero / SRC <strong>0.0–12.0 S</strong></span>
352
+ <span>Frame <strong id="frame-counter"></strong> / 359</span>
353
+ <span class="accent">Frame-seeked</span>
354
+ <span>12.00 S · 30 FPS</span>
355
+ </div>
356
+ </div>
357
+
358
+ <div id="outro-fade" data-fy-anim-start="329" data-fy-anim-duration="30"></div>
359
+ </div>
360
+ </fy-composition>
361
+ </body>
362
+ </html>