@lalalic/markcut 1.0.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 (98) hide show
  1. package/.env.example +27 -0
  2. package/.github/user-steer.md +9 -0
  3. package/.vscode/settings.json +3 -0
  4. package/AGENTS.md +271 -0
  5. package/README.md +219 -0
  6. package/SKILL.md +209 -0
  7. package/docs/dynamic-components.md +191 -0
  8. package/docs/edit-mode.md +220 -0
  9. package/docs/json-descriptive.md +539 -0
  10. package/docs/label-mode.md +110 -0
  11. package/docs/markdown-descriptive.md +751 -0
  12. package/docs/templates.md +52 -0
  13. package/package.json +64 -0
  14. package/remotion.config.ts +5 -0
  15. package/scripts/artlist-dl.mjs +190 -0
  16. package/scripts/build-pipeline.sh +19 -0
  17. package/scripts/build-player.sh +20 -0
  18. package/src/Root.tsx +55 -0
  19. package/src/config.mjs +88 -0
  20. package/src/context/EventContext.tsx +168 -0
  21. package/src/context/index.tsx +42 -0
  22. package/src/descriptive/compiler.test.ts +1135 -0
  23. package/src/descriptive/compiler.ts +1230 -0
  24. package/src/descriptive/dsl.ts +455 -0
  25. package/src/descriptive/markdown.test.ts +866 -0
  26. package/src/descriptive/markdown.ts +674 -0
  27. package/src/descriptive/resolve.test.ts +951 -0
  28. package/src/descriptive/resolve.ts +891 -0
  29. package/src/entry.tsx +163 -0
  30. package/src/index.ts +4 -0
  31. package/src/player/browser.tsx +356 -0
  32. package/src/player/bundle/player.js +60259 -0
  33. package/src/player/bundler.mjs +269 -0
  34. package/src/player/label-server.mjs +599 -0
  35. package/src/player/pipeline.mjs +11123 -0
  36. package/src/player/pipeline.ts +117 -0
  37. package/src/player/server-shared.mjs +144 -0
  38. package/src/player/server.mjs +1006 -0
  39. package/src/render/cli-tools.ts +177 -0
  40. package/src/render/cli.mjs +628 -0
  41. package/src/schema/index.ts +259 -0
  42. package/src/types/Audio.tsx +56 -0
  43. package/src/types/Component.tsx +135 -0
  44. package/src/types/Effect.tsx +88 -0
  45. package/src/types/Folder.tsx +180 -0
  46. package/src/types/FrameSyncStyle.tsx +51 -0
  47. package/src/types/Image.tsx +51 -0
  48. package/src/types/Include.tsx +394 -0
  49. package/src/types/Map.tsx +252 -0
  50. package/src/types/Rhythm.tsx +58 -0
  51. package/src/types/Scene.tsx +42 -0
  52. package/src/types/Subtitle.tsx +218 -0
  53. package/src/types/Video.tsx +70 -0
  54. package/src/types/keyframes.ts +454 -0
  55. package/src/utils/__tests__/vtt.test.ts +129 -0
  56. package/src/utils/index.ts +168 -0
  57. package/src/utils/tween.ts +118 -0
  58. package/src/vision/cli.mjs +1187 -0
  59. package/src/vision/vision_prompts.md +67 -0
  60. package/tests/dsl.test.ts +317 -0
  61. package/tests/fixtures/audio.json +25 -0
  62. package/tests/fixtures/basic.json +27 -0
  63. package/tests/fixtures/component-all.json +38 -0
  64. package/tests/fixtures/components.json +38 -0
  65. package/tests/fixtures/effects.json +64 -0
  66. package/tests/fixtures/full.json +51 -0
  67. package/tests/fixtures/map.json +23 -0
  68. package/tests/fixtures/md/all-nodes.md +28 -0
  69. package/tests/fixtures/md/basic.md +6 -0
  70. package/tests/fixtures/md/component-imports.md +20 -0
  71. package/tests/fixtures/md/edge-cases.md +33 -0
  72. package/tests/fixtures/md/effects.md +17 -0
  73. package/tests/fixtures/md/frontmatter.md +20 -0
  74. package/tests/fixtures/md/full-feature.md +58 -0
  75. package/tests/fixtures/md/imports-block.md +19 -0
  76. package/tests/fixtures/md/include-main.md +11 -0
  77. package/tests/fixtures/md/include-sub.md +25 -0
  78. package/tests/fixtures/md/jsx-code-fence.md +21 -0
  79. package/tests/fixtures/md/map.md +11 -0
  80. package/tests/fixtures/md/nested-scenes.md +25 -0
  81. package/tests/fixtures/md/rhythm.md +17 -0
  82. package/tests/fixtures/md/scenes.md +16 -0
  83. package/tests/fixtures/md/tween.md +11 -0
  84. package/tests/fixtures/md/vars-test.md +6 -0
  85. package/tests/fixtures/scenes.json +40 -0
  86. package/tests/fixtures/subtitle.json +59 -0
  87. package/tests/fixtures/subvideo.json +59 -0
  88. package/tests/fixtures/templates/courseware.md +351 -0
  89. package/tests/fixtures/tween-visual.json +28 -0
  90. package/tests/fixtures/video-series.json +54 -0
  91. package/tests/md-descriptive.test.ts +742 -0
  92. package/tests/render.test.ts +985 -0
  93. package/tests/schema.test.ts +68 -0
  94. package/tests/server.test.ts +308 -0
  95. package/tests/utils.ts +391 -0
  96. package/tests/vitest.config.ts +18 -0
  97. package/tests/vitest.integration.config.ts +16 -0
  98. package/tsconfig.json +20 -0
@@ -0,0 +1,67 @@
1
+ # Vision Understanding Prompts
2
+
3
+ These prompts are used by the `markcut vision` CLI to analyze images and videos.
4
+ Each section is a named prompt template wrapped in `~~~md` fences.
5
+ Override any prompt with `--<prompt-name> "your prompt text"`.
6
+ Provide scene/character context with `--context "the girl is Maggie, the man in red is me"`.
7
+
8
+ ## image-perception
9
+
10
+ Analyze the image and produce a detailed description.
11
+
12
+ ~~~md
13
+ Describe this image in detail. What do you see? Include the setting, colors, objects, people, weather, and mood. Write at least 3-4 sentences.
14
+
15
+ Be specific and thorough.
16
+ ~~~
17
+
18
+ ## video-perception
19
+
20
+ Analyze the video and produce a detailed description.
21
+
22
+ ~~~md
23
+ You are given several still frames sampled from a video. Based on these frames, describe the video scene in detail. What setting, subjects, actions, visual style, and mood do you observe? Write at least 3-4 sentences.
24
+
25
+ Be specific and thorough. Note changes between frames — they represent different moments in time.
26
+ ~~~
27
+
28
+ ## detect-scenes
29
+
30
+ Analyze the merged subtitle/transcript with candidate segment boundaries and split into meaningful segments with descriptions.
31
+
32
+ ~~~md
33
+ You are an expert video editor. You are given candidate segment boundaries for a video, gathered from up to three sources (ordered by reliability):
34
+
35
+ 1. **User hints** (most reliable) — explicit timestamps with descriptions provided by the user
36
+ 2. **Speech subtitles (VTT)** — subtitle cues with timestamps and transcript text
37
+ 3. **Visual scene changes** (least reliable) — raw ffprobe shot-detection timestamps
38
+
39
+ Your task: merge these candidate boundaries into meaningful, coherent segments.
40
+
41
+ Rules:
42
+ - Each final segment should be a coherent narrative or visual unit (5-60 seconds)
43
+ - Respect user hint boundaries — they are the most reliable. If the user marked a boundary at 5s, keep it
44
+ - Use subtitle cues to refine: if a subtitle sentence crosses a user hint boundary, prefer the cue boundary
45
+ - Use ffprobe changes as tiebreakers only — they mark raw visual cuts, not narrative breaks
46
+ - Output ONLY a JSON object where keys are time ranges (milliseconds) and values are objects with a "description" field
47
+ - Descriptions: 5-20 words, summarize what happens in that segment (use user hint text or subtitle text)
48
+
49
+ Input format:
50
+ ```
51
+ Candidates:
52
+ - 5000ms [userHint]: "开始游泳"
53
+ - 15250ms [userHint]: "beach scene"
54
+ - 3200ms [subtitle]: "Welcome to the beach everyone"
55
+ - 8500ms [subtitle]: "Let's go swimming in the ocean"
56
+ - 16000ms [subtitle]: "Now let's build a sandcastle"
57
+ - 4800ms [scene]: ffprobe shot change
58
+ - 15300ms [scene]: ffprobe shot change
59
+
60
+ Duration: 30000ms
61
+ ```
62
+
63
+ Output format:
64
+ {"0to5000": {"description": "Introduction and welcome"}, "5000to15250": {"description": "Starting to swim"}, "15250to30000": {"description": "Building sandcastle"}}
65
+
66
+ If inputs are too sparse for segmentation, return an empty object {}.
67
+ ~~~
@@ -0,0 +1,317 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ DslError,
4
+ LAYOUT_VALUES,
5
+ TRANSITION_VALUES,
6
+ isQuoted,
7
+ unquote,
8
+ splitTokens,
9
+ parseNumberMaybe,
10
+ parseWaypoints,
11
+ parseProps,
12
+ parseOnSpec,
13
+ parseEffects,
14
+ parseKeyValueTokens,
15
+ } from "../src/descriptive/dsl";
16
+
17
+ describe("dsl — string helpers", () => {
18
+ it("isQuoted detects double-quoted tokens", () => {
19
+ expect(isQuoted('"hello"')).toBe(true);
20
+ expect(isQuoted('""')).toBe(true);
21
+ expect(isQuoted("hello")).toBe(false);
22
+ expect(isQuoted('"unterminated')).toBe(false);
23
+ expect(isQuoted('"')).toBe(false);
24
+ });
25
+
26
+ it("unquote strips surrounding double quotes", () => {
27
+ expect(unquote('"hello"')).toBe("hello");
28
+ expect(unquote("hello")).toBe("hello");
29
+ expect(unquote('""')).toBe("");
30
+ });
31
+ });
32
+
33
+ describe("dsl — splitTokens", () => {
34
+ it("splits on whitespace", () => {
35
+ expect(splitTokens("a b c")).toEqual(["a", "b", "c"]);
36
+ });
37
+
38
+ it("keeps quoted spans together", () => {
39
+ expect(splitTokens('a "b c" d')).toEqual(["a", '"b c"', "d"]);
40
+ });
41
+
42
+ it("keeps curly-brace groups together", () => {
43
+ expect(splitTokens('subtitle:{fontSize:"20px"}')).toEqual(['subtitle:{fontSize:"20px"}']);
44
+ });
45
+
46
+ it("keeps bracket groups together", () => {
47
+ expect(splitTokens("effects:[fadeIn, bounceIn]")).toEqual(["effects:[fadeIn, bounceIn]"]);
48
+ });
49
+
50
+ it("keeps paren groups together", () => {
51
+ expect(splitTokens("on:(start, slide1.current=1)")).toEqual(["on:(start, slide1.current=1)"]);
52
+ });
53
+
54
+ it("handles nested brackets", () => {
55
+ expect(splitTokens("a:[1,[2,3]] b")).toEqual(["a:[1,[2,3]]", "b"]);
56
+ });
57
+
58
+ it("collapses runs of whitespace", () => {
59
+ expect(splitTokens("a b\tc")).toEqual(["a", "b", "c"]);
60
+ });
61
+
62
+ it("returns empty array for blank input", () => {
63
+ expect(splitTokens(" ")).toEqual([]);
64
+ });
65
+ });
66
+
67
+ describe("dsl — parseNumberMaybe", () => {
68
+ it("parses integers", () => {
69
+ expect(parseNumberMaybe("42")).toBe(42);
70
+ });
71
+
72
+ it("parses floats", () => {
73
+ expect(parseNumberMaybe("3.14")).toBe(3.14);
74
+ });
75
+
76
+ it("parses negative numbers", () => {
77
+ expect(parseNumberMaybe("-5")).toBe(-5);
78
+ });
79
+
80
+ it("parses booleans", () => {
81
+ expect(parseNumberMaybe("true")).toBe(true);
82
+ expect(parseNumberMaybe("false")).toBe(false);
83
+ });
84
+
85
+ it("returns strings unchanged when not numeric", () => {
86
+ expect(parseNumberMaybe("hello")).toBe("hello");
87
+ expect(parseNumberMaybe("20px")).toBe("20px");
88
+ });
89
+ });
90
+
91
+ describe("dsl — parseWaypoints", () => {
92
+ it("parses multiple semicolon-separated waypoints", () => {
93
+ expect(parseWaypoints('[40.7,-74.0,"NYC"; 34.05,-118.25,"LA"]')).toEqual([
94
+ { lat: 40.7, lng: -74.0, label: "NYC" },
95
+ { lat: 34.05, lng: -118.25, label: "LA" },
96
+ ]);
97
+ });
98
+
99
+ it("parses waypoints without labels", () => {
100
+ expect(parseWaypoints("[40.7,-74.0]")).toEqual([{ lat: 40.7, lng: -74.0, label: undefined }]);
101
+ });
102
+
103
+ it("returns empty array for non-bracket input", () => {
104
+ expect(parseWaypoints("not a list")).toEqual([]);
105
+ });
106
+
107
+ it("returns empty array for empty brackets", () => {
108
+ expect(parseWaypoints("[]")).toEqual([]);
109
+ });
110
+ });
111
+
112
+ describe("dsl — parseProps", () => {
113
+ it("parses standard JSON objects", () => {
114
+ expect(parseProps('{"a":1,"b":"x"}')).toEqual({ a: 1, b: "x" });
115
+ });
116
+
117
+ it("parses standard JSON arrays", () => {
118
+ expect(parseProps("[1,2,3]")).toEqual([1, 2, 3]);
119
+ });
120
+
121
+ it("leniently parses unquoted keys", () => {
122
+ expect(parseProps("{fontSize:20}")).toEqual({ fontSize: 20 });
123
+ });
124
+
125
+ it("leniently parses unquoted string values", () => {
126
+ expect(parseProps('{name:John}')).toEqual({ name: "John" });
127
+ });
128
+
129
+ it("returns {} for non-object input", () => {
130
+ expect(parseProps("hello")).toEqual({});
131
+ });
132
+
133
+ it("returns {} for unclosed object", () => {
134
+ expect(parseProps("{a:1")).toEqual({});
135
+ });
136
+ });
137
+
138
+ describe("dsl — parseOnSpec", () => {
139
+ it("parses a two-argument on() spec", () => {
140
+ expect(parseOnSpec("(start, slide1.current=1)")).toEqual({
141
+ when: "start",
142
+ state: "slide1.current=1",
143
+ });
144
+ });
145
+
146
+ it("joins extra comma-separated state parts", () => {
147
+ expect(parseOnSpec("(start, a=1, b=2)")).toEqual({
148
+ when: "start",
149
+ state: "a=1,b=2",
150
+ });
151
+ });
152
+
153
+ it("returns undefined for non-paren input", () => {
154
+ expect(parseOnSpec("start")).toBeUndefined();
155
+ });
156
+
157
+ it("returns undefined for single argument", () => {
158
+ expect(parseOnSpec("(start)")).toBeUndefined();
159
+ });
160
+
161
+ it("returns undefined for empty parens", () => {
162
+ expect(parseOnSpec("()")).toBeUndefined();
163
+ });
164
+ });
165
+
166
+ describe("dsl — parseEffects", () => {
167
+ it("parses a single effect", () => {
168
+ expect(parseEffects("[fadeIn]")).toEqual(["fadeIn"]);
169
+ });
170
+
171
+ it("parses multiple comma-separated effects", () => {
172
+ expect(parseEffects("[fadeIn, bounceIn]")).toEqual(["fadeIn", "bounceIn"]);
173
+ });
174
+
175
+ it("parses parameterized effects", () => {
176
+ expect(parseEffects("[fadeIn(timingFunction:ease-out iterationCount:2)]")).toEqual([
177
+ "fadeIn(timingFunction:ease-out iterationCount:2)",
178
+ ]);
179
+ });
180
+
181
+ it("returns empty array for non-bracket input", () => {
182
+ expect(parseEffects("fadeIn")).toEqual([]);
183
+ });
184
+
185
+ it("returns empty array for empty brackets", () => {
186
+ expect(parseEffects("[]")).toEqual([]);
187
+ });
188
+ });
189
+
190
+ describe("dsl — parseKeyValueTokens", () => {
191
+ it("parses simple key:value pairs", () => {
192
+ expect(parseKeyValueTokens(["src:a.mp4", "duration:2"])).toEqual({
193
+ src: "a.mp4",
194
+ duration: 2,
195
+ });
196
+ });
197
+
198
+ it("coerces numeric and boolean values", () => {
199
+ expect(parseKeyValueTokens(["duration:2", "loop:true", "volume:0.8"])).toEqual({
200
+ duration: 2,
201
+ loop: true,
202
+ volume: 0.8,
203
+ });
204
+ });
205
+
206
+ it("preserves raw string keys (jsx, prompt, instruction) verbatim", () => {
207
+ expect(parseKeyValueTokens(['jsx:"<Foo/>"'])).toEqual({ jsx: "<Foo/>" });
208
+ expect(parseKeyValueTokens(["instruction:hello world"])).toEqual({ instruction: "hello world" });
209
+ });
210
+
211
+ it("peeks next quoted token when value after colon is empty", () => {
212
+ expect(parseKeyValueTokens(['instruction:', '"fast opener"'])).toEqual({
213
+ instruction: "fast opener",
214
+ });
215
+ });
216
+
217
+ it("parses inline object values as lenient JSON", () => {
218
+ expect(parseKeyValueTokens(['subtitle:{fontSize:"20px"}'])).toEqual({
219
+ subtitle: { fontSize: "20px" },
220
+ });
221
+ });
222
+
223
+ it("dispatches on:(...) to parseOnSpec", () => {
224
+ expect(parseKeyValueTokens(["on:(start, slide1.current=1)"])).toEqual({
225
+ on: { when: "start", state: "slide1.current=1" },
226
+ });
227
+ });
228
+
229
+ it("rejects the bare key(value) paren form (colon required)", () => {
230
+ expect(() => parseKeyValueTokens(["on(start, slide1.current=1)"])).toThrow(DslError);
231
+ expect(() => parseKeyValueTokens(["on(start, slide1.current=1)"])).toThrow(/unrecognized token/);
232
+ });
233
+
234
+ it("dispatches effects=[...] to parseEffects", () => {
235
+ expect(parseKeyValueTokens(["effects:[fadeIn, bounceIn]"])).toEqual({
236
+ effects: ["fadeIn", "bounceIn"],
237
+ });
238
+ });
239
+
240
+ it("validates layout against the enum", () => {
241
+ expect(parseKeyValueTokens(["layout:series"])).toEqual({ layout: "series" });
242
+ expect(() => parseKeyValueTokens(["layout:bogus"])).toThrow(DslError);
243
+ expect(() => parseKeyValueTokens(["layout:bogus"])).toThrow(/invalid layout value/);
244
+ });
245
+
246
+ it("validates transition against the enum", () => {
247
+ expect(parseKeyValueTokens(["transition:fade"])).toEqual({ transition: "fade" });
248
+ expect(() => parseKeyValueTokens(["transition:bogus"])).toThrow(/invalid transition value/);
249
+ });
250
+
251
+ it("parses merged transition:fade(0.5) into transition + transitionTime", () => {
252
+ expect(parseKeyValueTokens(["transition:fade(0.5)"])).toEqual({
253
+ transition: "fade",
254
+ transitionTime: 0.5,
255
+ });
256
+ });
257
+
258
+ it("throws DslError on unrecognized tokens", () => {
259
+ expect(() => parseKeyValueTokens(["@@bogus@@"])).toThrow(DslError);
260
+ expect(() => parseKeyValueTokens(["@@bogus@@"])).toThrow(/unrecognized token/);
261
+ });
262
+
263
+ it("includes line context in error messages when provided", () => {
264
+ const ctx = { line: 8, lineText: "layout:bogus" };
265
+ try {
266
+ parseKeyValueTokens(["layout:bogus"], ctx);
267
+ expect.fail("should have thrown");
268
+ } catch (e) {
269
+ expect(e).toBeInstanceOf(DslError);
270
+ const msg = (e as DslError).message;
271
+ expect(msg).toContain("line 8");
272
+ expect(msg).toContain("layout:bogus");
273
+ }
274
+ });
275
+
276
+ it("includes token context in error messages", () => {
277
+ try {
278
+ parseKeyValueTokens(["layout:bogus"], { line: 1 });
279
+ expect.fail("should have thrown");
280
+ } catch (e) {
281
+ expect((e as DslError).message).toContain('near "layout:bogus"');
282
+ }
283
+ });
284
+ });
285
+
286
+ describe("dsl — DslError", () => {
287
+ it("formats a message with line and token context", () => {
288
+ const err = new DslError("bad value", { line: 5, token: "foo", lineText: "foo bar" });
289
+ expect(err.message).toContain("bad value");
290
+ expect(err.message).toContain("line 5");
291
+ expect(err.message).toContain('near "foo"');
292
+ expect(err.message).toContain("foo bar");
293
+ expect(err.name).toBe("DslError");
294
+ });
295
+
296
+ it("formats a message without context", () => {
297
+ const err = new DslError("something broke");
298
+ expect(err.message).toBe("something broke");
299
+ });
300
+ });
301
+
302
+ describe("dsl — exported constants", () => {
303
+ it("LAYOUT_VALUES contains the four layout types", () => {
304
+ expect(LAYOUT_VALUES.has("series")).toBe(true);
305
+ expect(LAYOUT_VALUES.has("parallel")).toBe(true);
306
+ expect(LAYOUT_VALUES.has("transitionSeries")).toBe(true);
307
+ expect(LAYOUT_VALUES.has("transition")).toBe(true);
308
+ });
309
+
310
+ it("TRANSITION_VALUES contains the five transition types", () => {
311
+ expect(TRANSITION_VALUES.has("fade")).toBe(true);
312
+ expect(TRANSITION_VALUES.has("slide")).toBe(true);
313
+ expect(TRANSITION_VALUES.has("wipe")).toBe(true);
314
+ expect(TRANSITION_VALUES.has("flip")).toBe(true);
315
+ expect(TRANSITION_VALUES.has("clockWipe")).toBe(true);
316
+ });
317
+ });
@@ -0,0 +1,25 @@
1
+ {
2
+ "id": "root",
3
+ "type": "root",
4
+ "width": 640,
5
+ "height": 480,
6
+ "fps": 30,
7
+ "isSeries": false,
8
+ "children": [
9
+ {
10
+ "id": "bgm",
11
+ "type": "audio",
12
+ "src": "assets/bgm/ambient-loop.mp3",
13
+ "volume": 0.3,
14
+ "isBackground": true,
15
+ "actions": [{ "start": 0, "end": 3 }]
16
+ },
17
+ {
18
+ "id": "bg",
19
+ "type": "image",
20
+ "src": "https://picsum.photos/seed/audio-test/640/480",
21
+ "fit": "cover",
22
+ "actions": [{ "start": 0, "end": 3 }]
23
+ }
24
+ ]
25
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "id": "root",
3
+ "type": "root",
4
+ "width": 640,
5
+ "height": 480,
6
+ "fps": 30,
7
+ "isSeries": true,
8
+ "transition": "fade",
9
+ "transitionTime": 0.3,
10
+ "children": [
11
+ {
12
+ "id": "scene-1",
13
+ "type": "folder",
14
+ "title": "Scene 1",
15
+ "isSeries": false,
16
+ "children": [
17
+ {
18
+ "id": "bg",
19
+ "type": "image",
20
+ "src": "https://picsum.photos/seed/test-basic/640/480",
21
+ "fit": "cover",
22
+ "actions": [{ "start": 0, "end": 2 }]
23
+ }
24
+ ]
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "id": "root",
3
+ "type": "root",
4
+ "width": 640,
5
+ "height": 480,
6
+ "fps": 30,
7
+ "isSeries": true,
8
+ "children": [
9
+ {
10
+ "id": "scene-html",
11
+ "type": "scene",
12
+ "name": "HTML-JSX",
13
+ "layout": "parallel",
14
+ "children": [
15
+ {
16
+ "id": "c-hello",
17
+ "type": "component",
18
+ "jsx": "<div style={{color:'#fff',fontSize:28,textAlign:'center'}}>Hello World</div>",
19
+ "actions": [{ "start": 0, "end": 2 }]
20
+ }
21
+ ]
22
+ },
23
+ {
24
+ "id": "scene-tween",
25
+ "type": "scene",
26
+ "name": "Tween-Animation",
27
+ "layout": "parallel",
28
+ "children": [
29
+ {
30
+ "id": "c-tween",
31
+ "type": "component",
32
+ "jsx": "<svg width=\"200\" height=\"200\" viewBox=\"0 0 200 200\"><rect x=\"0\" y={tween(0,150)} width=\"50\" height={tween(50,200)} fill=\"#667eea\" /><rect x=\"70\" y={tween(50,100)} width=\"50\" height={tween(100,50)} fill=\"#764ba2\" /><rect x=\"140\" y={tween(100,0)} width=\"50\" height={tween(50,200)} fill=\"#f093fb\" /></svg>",
33
+ "actions": [{ "start": 0, "end": 3 }]
34
+ }
35
+ ]
36
+ }
37
+ ]
38
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "id": "root",
3
+ "type": "root",
4
+ "width": 640,
5
+ "height": 480,
6
+ "fps": 30,
7
+ "isSeries": true,
8
+ "transition": "fade",
9
+ "transitionTime": 0.3,
10
+ "children": [
11
+ {
12
+ "id": "scene-1",
13
+ "type": "folder",
14
+ "isSeries": false,
15
+ "children": [
16
+ {
17
+ "id": "headline",
18
+ "type": "component",
19
+ "jsx": "<div style={{background:'linear-gradient(135deg,#667eea,#764ba2)',padding:'40px',borderRadius:'16px',textAlign:'center'}}><h1 style={{color:'white',fontSize:'36px',margin:0}}>Component Test</h1><p style={{color:'rgba(255,255,255,0.7)',fontSize:'18px'}}>Built-in components render correctly</p></div>",
20
+ "actions": [{ "start": 0, "end": 3 }]
21
+ }
22
+ ]
23
+ },
24
+ {
25
+ "id": "scene-2",
26
+ "type": "folder",
27
+ "isSeries": false,
28
+ "children": [
29
+ {
30
+ "id": "stats",
31
+ "type": "component",
32
+ "jsx": "<div style={{background:'#1a1a2e',padding:'30px',borderRadius:'16px',textAlign:'center'}}><span style={{color:'#667eea',fontSize:'64px',fontWeight:'bold'}}>100%</span><p style={{color:'rgba(255,255,255,0.6)',fontSize:'16px'}}>Working</p></div>",
33
+ "actions": [{ "start": 0, "end": 3 }]
34
+ }
35
+ ]
36
+ }
37
+ ]
38
+ }
@@ -0,0 +1,64 @@
1
+ {
2
+ "id": "root",
3
+ "type": "root",
4
+ "width": 640,
5
+ "height": 480,
6
+ "fps": 30,
7
+ "isSeries": true,
8
+ "transition": "fade",
9
+ "transitionTime": 0.3,
10
+ "stylesheet": ".effect { overflow: hidden; }",
11
+ "children": [
12
+ {
13
+ "id": "fade-in",
14
+ "type": "effect",
15
+ "animation": "fadeIn",
16
+ "animationTimingFunction": "ease-in-out",
17
+ "children": [
18
+ {
19
+ "id": "bg-fade",
20
+ "type": "image",
21
+ "src": "https://picsum.photos/seed/effect-fade/640/480",
22
+ "fit": "cover",
23
+ "actions": [{ "start": 0, "end": 2 }]
24
+ }
25
+ ],
26
+ "actions": [{ "start": 0, "end": 2 }]
27
+ },
28
+ {
29
+ "id": "bounce",
30
+ "type": "effect",
31
+ "animation": "bounceIn",
32
+ "children": [
33
+ {
34
+ "id": "bg-bounce",
35
+ "type": "image",
36
+ "src": "https://picsum.photos/seed/effect-bounce/640/480",
37
+ "fit": "cover",
38
+ "actions": [{ "start": 0, "end": 2 }]
39
+ }
40
+ ],
41
+ "actions": [{ "start": 0, "end": 2 }]
42
+ },
43
+ {
44
+ "id": "custom-kf",
45
+ "type": "effect",
46
+ "animation": "custom",
47
+ "customKeyframes": {
48
+ "0": { "opacity": "0", "transform": "scale(0.5) rotate(-10deg)" },
49
+ "50": { "opacity": "1", "transform": "scale(1.1) rotate(5deg)" },
50
+ "100": { "opacity": "1", "transform": "scale(1) rotate(0deg)" }
51
+ },
52
+ "children": [
53
+ {
54
+ "id": "bg-custom",
55
+ "type": "image",
56
+ "src": "https://picsum.photos/seed/effect-custom/640/480",
57
+ "fit": "cover",
58
+ "actions": [{ "start": 0, "end": 2 }]
59
+ }
60
+ ],
61
+ "actions": [{ "start": 0, "end": 2 }]
62
+ }
63
+ ]
64
+ }
@@ -0,0 +1,51 @@
1
+ {
2
+ "id": "root",
3
+ "type": "root",
4
+ "width": 640,
5
+ "height": 480,
6
+ "fps": 30,
7
+ "isSeries": false,
8
+ "children": [
9
+ {
10
+ "id": "bgm",
11
+ "type": "audio",
12
+ "src": "assets/bgm/ambient-loop.mp3",
13
+ "volume": 0.15,
14
+ "actions": [{ "start": 0, "end": 5 }]
15
+ },
16
+ {
17
+ "id": "scene-1",
18
+ "type": "folder",
19
+ "isSeries": false,
20
+ "children": [
21
+ {
22
+ "id": "bg1",
23
+ "type": "component",
24
+ "jsx": "<div style={{position:'absolute',inset:0,background:'radial-gradient(circle at center,#667eea,#764ba2)'}} />",
25
+ "isBackground": true,
26
+ "actions": [{ "start": 0, "end": 5 }]
27
+ },
28
+ {
29
+ "id": "headline",
30
+ "type": "component",
31
+ "jsx": "<div style={{background:'linear-gradient(135deg,#667eea,#764ba2)',padding:'40px',borderRadius:'16px',textAlign:'center'}}><h1 style={{color:'white',fontSize:'32px',margin:0}}>Full Feature Test</h1><p style={{color:'rgba(255,255,255,0.7)',fontSize:'16px'}}>All systems operational</p></div>",
32
+ "actions": [{ "start": 0.3, "end": 4.5 }]
33
+ }
34
+ ]
35
+ },
36
+ {
37
+ "id": "scene-2",
38
+ "type": "folder",
39
+ "isSeries": false,
40
+ "children": [
41
+ {
42
+ "id": "stats",
43
+ "type": "component",
44
+ "type": "component",
45
+ "jsx": "<div style={{background:'#1a1a2e',padding:'30px',borderRadius:'16px',textAlign:'center'}}><span style={{color:'#667eea',fontSize:'64px',fontWeight:'bold'}}>100%</span><p style={{color:'rgba(255,255,255,0.6)',fontSize:'16px'}}>Coverage</p></div>",
46
+ "actions": [{ "start": 0.5, "end": 4.5 }]
47
+ }
48
+ ]
49
+ }
50
+ ]
51
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "id": "root",
3
+ "type": "root",
4
+ "width": 640,
5
+ "height": 480,
6
+ "fps": 30,
7
+ "isSeries": false,
8
+ "children": [
9
+ {
10
+ "id": "route",
11
+ "type": "map",
12
+ "name": "sf-to-la",
13
+ "waypoints": [
14
+ { "lat": 37.7749, "lng": -122.4194, "label": "SF" },
15
+ { "lat": 36.7783, "lng": -119.4179, "label": "Fresno" },
16
+ { "lat": 34.0522, "lng": -118.2437, "label": "LA" }
17
+ ],
18
+ "routeColor": "#4285F4",
19
+ "routeWeight": 5,
20
+ "actions": [{ "start": 0, "end": 3 }]
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,28 @@
1
+ # video
2
+ width:640 height:480 fps:30 layout:series instruction:"Full node type demo" metadata:"all-nodes"
3
+ ## Media
4
+ layout:parallel
5
+ - image src:photo.jpg duration:3 fit:cover
6
+ - video src:movie.mp4 startFrom:0.5 endAt:3.5 volume:0.9 playbackRate:1.2
7
+ - audio src:bgm.mp3 duration:4 volume:0.5 foreground:true
8
+ ## Components
9
+ layout:parallel
10
+ - component duration:3 jsx:"<AnimatedHeadline text='Hello World' gradient />"
11
+ - component duration:2 jsx:"<div style={{color:'red'}}>Inline</div>"
12
+ ## Effects
13
+ layout:parallel
14
+ - image src:card.jpg duration:2 fit:cover effects:[fadeIn(3)]
15
+ - image src:badge.png duration:1 effects:[bounceIn(,ease-out,2)]
16
+ ## Rhythm
17
+ layout:parallel
18
+ - rhythm src:beat.mp3 spots:[0.5,1.2,1.9,2.8] volume:0.8
19
+ - image src:flash1.jpg
20
+ - image src:flash2.jpg
21
+ - image src:flash3.jpg
22
+ - image src:flash4.jpg
23
+ ## Map
24
+ layout:parallel
25
+ - map duration:4 travelMode:DRIVING waypoints:[37.77,-122.41,"SF";34.05,-118.24,"LA";36.16,-115.15,"LV"]
26
+ ## Include
27
+ layout:parallel
28
+ - include src:./child.json duration:3