reelkit-cli 0.8.0 → 0.10.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/README.md +3 -1
- package/package.json +49 -10
- package/skill/SKILL.md +29 -19
- package/skill/THIRD_PARTY.md +3 -3
- package/skill/commands/launch-film.md +1 -1
- package/skill/reference/art-styles.md +70 -0
- package/skill/reference/backgrounds.md +1 -1
- package/skill/reference/brand-motion.md +62 -0
- package/skill/reference/clips.md +3 -3
- package/skill/reference/component-authoring.md +1 -1
- package/skill/reference/continuity.md +5 -5
- package/skill/reference/delivery-review.md +40 -0
- package/skill/reference/hebrew-rtl.md +3 -4
- package/skill/reference/{remotion-composition.md → hyperframes-composition.md} +14 -8
- package/skill/reference/kit.md +7 -3
- package/skill/reference/launch-film.md +13 -9
- package/skill/reference/motion-design.md +18 -16
- package/skill/reference/sound-design.md +34 -12
- package/skill/reference/studio-editing.md +55 -0
- package/skill/reference/styles.md +9 -6
- package/skill/reference/three-d.md +8 -7
- package/skill/reference/voice-sync.md +1 -1
- package/src/commands/assets.ts +5 -1
- package/src/contract/index.ts +1 -1
- package/src/hyperframes/Root.tsx +1 -0
- package/src/hyperframes/fonts.ts +54 -0
- package/src/hyperframes/frame.tsx +46 -0
- package/src/hyperframes/host.tsx +38 -0
- package/src/{remotion → hyperframes}/kit/Assemble3D.tsx +1 -1
- package/src/hyperframes/kit/BrandTransform3D.tsx +12 -0
- package/src/{remotion → hyperframes}/kit/BrowserFrame.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Camera.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Captions.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Card3D.tsx +2 -2
- package/src/{remotion → hyperframes}/kit/Carry.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/ChapterFrame.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/ClipLayer.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Counter.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/CounterRoll.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Entrance.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/FootageLayer.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/GlassPanel.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Grounds.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Headline.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Hero3D.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/HudOverlay.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/ImageLayers.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/KenBurnsImage.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/KeyedClip.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Layers.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/LowerThird.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Music.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/NamedCursor.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Orbit3D.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Particles3D.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Place.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/PromptBox.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Scene3D.tsx +2 -2
- package/src/{remotion → hyperframes}/kit/SceneFrame.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/ScreenOverlay.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Sfx.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/SoundCues.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/TerminalLog.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Text3D.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/TextOnImage.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/TitleCard.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Voiceover.tsx +1 -1
- package/src/{remotion → hyperframes}/kit/Warp3D.tsx +1 -1
- package/src/hyperframes/kit/brand-transform.ts +25 -0
- package/src/{remotion → hyperframes}/kit/docs.ts +6 -2
- package/src/{remotion → hyperframes}/kit/index.ts +3 -0
- package/src/{remotion → hyperframes}/kit/sound-cues.ts +1 -1
- package/src/{remotion → hyperframes}/kit/sound-kinds.ts +14 -1
- package/src/{remotion → hyperframes}/kit/theme.ts +42 -40
- package/src/{remotion → hyperframes}/kit/ui-math.ts +1 -1
- package/src/hyperframes/math.ts +62 -0
- package/src/hyperframes/three.tsx +10 -0
- package/src/project/chromakey.ts +1 -1
- package/src/project/layers.ts +1 -1
- package/src/project/serve.ts +2 -2
- package/src/render/component-preview.ts +11 -55
- package/src/render/render.ts +62 -64
- package/src/render/serve.ts +31 -0
- package/src/render/static-check.ts +15 -4
- package/src/render/validate.ts +4 -4
- package/src/render/word-check.ts +1 -1
- package/src/render/worker.ts +71 -0
- package/src/testing/fixtures.ts +1 -1
- package/src/remotion/Root.tsx +0 -31
- /package/src/{remotion → hyperframes}/kit/Icon.tsx +0 -0
- /package/src/{remotion → hyperframes}/kit/beat.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/bg-math.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/brand-icons.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/caption-groups.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/caption-style.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/image-layers-math.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/inter-bold-typeface.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/media.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/motion-math.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/music-math.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/quiet-three.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/sample-text.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/scene3d-context.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/seeded.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/three-fx-math.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/three-math.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/transition-math.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/ui-theme.ts +0 -0
- /package/src/{remotion → hyperframes}/kit/word-anchor.ts +0 -0
- /package/src/{remotion → hyperframes}/types.ts +0 -0
|
@@ -5,9 +5,9 @@ description: Use before planning, to choose the video's look with the user - the
|
|
|
5
5
|
|
|
6
6
|
# Choosing the look
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Choose a visual language that fits the material and record it in the first scene's `notes`. Brand films usually benefit from one system; a narration-driven mixed-media film can change worlds by meaning while retaining a thread of type, accent or object. Read `reference/studio-editing.md` when the approach is still open.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Use the user's chosen direction. Otherwise recommend a fitting look with a short reason; use the catalogue as technique references, not a mandatory menu. Offer alternatives when requested. Ask only about consequential missing information.
|
|
11
11
|
|
|
12
12
|
## Two modes
|
|
13
13
|
Every look belongs to one mode. The mode decides which rules in `reference/motion-design.md` bend.
|
|
@@ -19,8 +19,8 @@ Every look belongs to one mode. The mode decides which rules in `reference/motio
|
|
|
19
19
|
|
|
20
20
|
**Showreel** (openers, announcements, anything that must stop the scroll)
|
|
21
21
|
- Hard cuts on the beat between full-colour backgrounds; type that fills the frame. Flat solid colour fields are right here.
|
|
22
|
-
-
|
|
23
|
-
-
|
|
22
|
+
- Camera movement and brief impact shakes can reinforce selected hits. Keep reading moments stable.
|
|
23
|
+
- Pace changes by meaning and musical phrasing, allowing deliberate holds rather than moving every frame.
|
|
24
24
|
- At most three full-screen flashes in any second, and never a red strobe. This is a safety rule, not a taste rule.
|
|
25
25
|
|
|
26
26
|
## The catalogue
|
|
@@ -57,7 +57,7 @@ Each look is built from what the kit and the shared library already hold, so mos
|
|
|
57
57
|
**When:** an opener or end card, from the user's own logo file. **How:** build toward the logo from its own shapes and colours and land on it exactly as supplied, held at least 1.5 seconds. **Forbidden:** redrawing, recolouring or stretching the logo; anyone else's logo or trademark.
|
|
58
58
|
|
|
59
59
|
### 11. Launch film (restrained)
|
|
60
|
-
**When:** a product, app or feature launch for a tech, software or AI brand: the short film that opens a launch post. **How:** a clean off-white or near-black ground with one soft pool of the hero colour at an edge (search "glow backdrop"); one object in the middle of each shot and a lot of empty space around it. Open on a single line of small, calm type that comes into focus word by word (search "blur reveal"), then one idea per shot, each 1.5 to 2.5 seconds: a field being typed into (search "search bar", "prompt box"), the real screenshot on a card floating at a slight tilt (search "tilt card", "laptop mockup", "phone mockup"), tiles circling a short line (search "orbit"), a feature grid (search "bento"), a pointer that clicks through it (search "cursor click"), the one real number as the payoff (search "statistic counter"), and the logo small and centred to close (search "logo reveal", "brand splash"). Between shots the outgoing object leaves fast with a short directional blur and the next one arrives on a heavy spring; the camera pushes in 2 to 4 percent on every shot. Six to eight shots in 12 to 20 seconds, music without a voice or with very few words. This look opens small on purpose: the first object may fill only a fifth of the frame, as long as the push carries it larger. **Forbidden:** two objects competing in one shot, a busy or textured background, a second accent colour, an invented screen,
|
|
60
|
+
**When:** a product, app or feature launch for a tech, software or AI brand: the short film that opens a launch post. **How:** a clean off-white or near-black ground with one soft pool of the hero colour at an edge (search "glow backdrop"); one object in the middle of each shot and a lot of empty space around it. Open on a single line of small, calm type that comes into focus word by word (search "blur reveal"), then one idea per shot, each 1.5 to 2.5 seconds: a field being typed into (search "search bar", "prompt box"), the real screenshot on a card floating at a slight tilt (search "tilt card", "laptop mockup", "phone mockup"), tiles circling a short line (search "orbit"), a feature grid (search "bento"), a pointer that clicks through it (search "cursor click"), the one real number as the payoff (search "statistic counter"), and the logo small and centred to close (search "logo reveal", "brand splash"). Between shots the outgoing object leaves fast with a short directional blur and the next one arrives on a heavy spring; the camera pushes in 2 to 4 percent on every shot. Six to eight shots in 12 to 20 seconds, music without a voice or with very few words. This look opens small on purpose: the first object may fill only a fifth of the frame, as long as the push carries it larger. **Forbidden:** two objects competing in one shot, a busy or textured background, a second accent colour, an invented screen, an unreadable hold, and a third-party mark unrelated to the material.
|
|
61
61
|
|
|
62
62
|
### 12. Social native (showreel)
|
|
63
63
|
**When:** the video should feel like it was made inside the app it is posted to: a reply to a comment, a reaction, proof from the user's own account. **How:** the picture is footage or a generated image and the graphics are the platform's own furniture, used honestly: the comment being answered at the top (search "comment sticker"), a post or a story in its frame (search "post frame", "story frame"), the reel's own buttons over the picture (search "reel overlay"), a profile with a Follow button to close (search "profile card"), reactions floating up on the payoff (search "emoji reactions", "emoji pop"), and words punching in one at a time on the voice (search "punch words"). Emoji are allowed in this look, as reactions and stickers only. **Forbidden:** an invented comment, post, follower count or like count; a platform's logo when the video is not about that platform; emoji standing in for a logo or an icon.
|
|
@@ -65,8 +65,11 @@ Each look is built from what the kit and the shared library already hold, so mos
|
|
|
65
65
|
### 13. AI at work (restrained)
|
|
66
66
|
**When:** an AI product, an assistant or an automation: the viewer should watch it think and deliver. **How:** near-black ground; the request typed into a glowing field and sent (search "prompt box", "assistant chat window"); the work shown as steps that spin and tick (search "agent steps") or as a placeholder that resolves into the result (search "shimmer skeleton"); the answer streaming in (search "streaming answer"); a calm orb when it listens or speaks (search "glow orb"). One glow, on the field or the orb, never both in one frame. Every answer on screen is the product's real output, supplied by the user. **Forbidden:** an answer the product did not give, a second glowing element, sparkles as decoration, text that streams faster than it can be read.
|
|
67
67
|
|
|
68
|
+
## Fifteen art styles (14 to 28)
|
|
69
|
+
`reference/art-styles.md` continues this list with fifteen looks whose picture is drawn in code rather than built from interface parts, adapted from the mg-styles-15 films: 14 flat vector, 15 line art, 16 isometric, 17 soft 3D render, 18 hand-drawn cel, 19 collage cut-out, 20 liquid, 21 shape morph, 22 Bauhaus grid, 23 synthwave, 24 aurora glass, 25 variety captions, 26 sticker explainer, 27 pixel art, 28 HUD. Use them as technique references, and read that file when the brief needs a style in that family (a drawn explainer, a line-art logo, an isometric city, "80s", "pixel", "glass", a kinetic poster). Each card there says which motion rules the look is allowed to bend.
|
|
70
|
+
|
|
68
71
|
## Your own
|
|
69
|
-
|
|
72
|
+
Use the supplied message, actions, colours, duration and format. Ask only for consequential missing details or a reference that the choice depends on. Choose the nearest look above as the technical base and write a four-line brief in the same shape (when, how, forbidden, structure). Show the brief with the plan at the checkpoint.
|
|
70
73
|
|
|
71
74
|
## What every look shares
|
|
72
75
|
- The plan checkpoint lists the exact words that will appear on screen and when. Fixing a list takes a minute; fixing a finished animation takes an hour.
|
|
@@ -5,21 +5,22 @@ description: Use when a shot needs real depth - extruded type, a screen floating
|
|
|
5
5
|
|
|
6
6
|
# 3D shots
|
|
7
7
|
|
|
8
|
-
The kit has a 3D layer on three.js: `Scene3D`, `Text3D`, `Card3D` and `Orbit3D`, and
|
|
8
|
+
The kit has a 3D layer on three.js: `Scene3D`, `Text3D`, `Card3D` and `Orbit3D`, and elements for the moments in a film that earn them: `Assemble3D`, `BrandTransform3D`, `Particles3D`, `Hero3D`, `Screen3D` and `Warp3D`. Everything is deterministic and driven by the frame. You import only from `reelkit/kit`; three itself is not importable from the composition.
|
|
9
9
|
|
|
10
10
|
## When 3D earns its place
|
|
11
11
|
|
|
12
12
|
One hero moment in a film: the product's name landing as thick letters, a screen floating in space and turning to face the viewer, a ring of features that the camera pushes through. That is what the extra depth is for.
|
|
13
13
|
|
|
14
|
-
It reads as a template when everything spins, when several objects each get their own 3D entrance, or when body text is extruded. Type that is read in a sentence stays 2D.
|
|
14
|
+
It reads as a template when everything spins, when several objects each get their own 3D entrance, or when body text is extruded. Type that is read in a sentence stays 2D. One or two 3D shots usually provide enough contrast. A requested brand film or showreel can use more when each has a distinct purpose; keep sentences and detailed screens readable.
|
|
15
15
|
|
|
16
16
|
## Which element earns its place
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Choose by what the moment has to say. For a brand transformation and its matching sound, read `reference/brand-motion.md`:
|
|
19
19
|
|
|
20
20
|
| The moment | Reach for | Why |
|
|
21
21
|
|---|---|---|
|
|
22
22
|
| A reveal: the name, a logo, a number of things arriving | `Assemble3D` (`shape="text"` for a word, `"grid"`, `"sphere"`, `"wall"`, `"ring"`) | Hundreds of blocks build into the thing; the piece counter (`assembledCount`) and the lock sound (`assembleEnd`) come with it |
|
|
23
|
+
| A brand or product arriving in its readable pose | `BrandTransform3D` (`mode="turn"`, `"arc"`, `"launch"`) | One deterministic arrival that holds neutral; `brandTransformFrames` gives the exact sound landing |
|
|
23
24
|
| An abstract idea: intelligence, flow, a network, "everything in one place" | `Particles3D` (`from` and `to` shapes, or `"text"`) | Light that changes shape reads as thought, and costs no artwork |
|
|
24
25
|
| The product itself | `Hero3D` (`kind="device"` with a screenshot, `"logo"`, `"coin"`, `"box"`) | A studio-lit object with clearcoat, two coloured rim lights and a soft floor |
|
|
25
26
|
| An interface carried across scenes | `Screen3D` with `poses` | One screen, re-posed at each scene, instead of a new mock each time |
|
|
@@ -27,7 +28,7 @@ One 3D moment per film is usually enough. Choose by what the moment has to say:
|
|
|
27
28
|
|
|
28
29
|
Set the mood once on the `Scene3D` (`mood="studio"`, `"night"`, `"sunset"` or `"neon"`): it chooses the light colours, their intensities and the fog tint together, so nobody tunes lights by hand. Everything fits a 9:16 frame by default (`fit`, the share of the width at the closest the camera comes), and everything is a pure function of the frame and a `seed`.
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
HyperFrames captures 3D through software GL for consistent output. Render cost depends on the browser, resolution, geometry and other jobs on the machine; measure the actual shot instead of relying on timings from a previous renderer. Start with the count the picture needs and raise it only when the preview improves.
|
|
31
32
|
|
|
32
33
|
## How to layer it
|
|
33
34
|
|
|
@@ -47,7 +48,7 @@ So on a phone frame the camera sees about 3 units across at z 6, not 5.5; a card
|
|
|
47
48
|
|
|
48
49
|
## Sizes: leave them out
|
|
49
50
|
|
|
50
|
-
`Text3D` without `size` is sized from the measured width of its own letters so that it fills `fit` of the frame's width (default 0.8) at the closest the camera comes to it. "Closest" is read from the `Scene3D` camera keys: the key where it sees the least. A word is therefore whole at every moment, on any aspect, and exactly as big as it can be. A line break
|
|
51
|
+
`Text3D` without `size` is sized from the measured width of its own letters so that it fills `fit` of the frame's width (default 0.8) at the closest the camera comes to it. "Closest" is read from the `Scene3D` camera keys: the key where it sees the least. A word is therefore whole at every moment, on any aspect, and exactly as big as it can be. A line break makes a second line: write `text={"Hyper\nFrames"}` in JSX, since `text="Hyper\nFrames"` passes a literal backslash and n; its height is held to `fit` of the frame's height. Giving `size` turns this off and you are back to doing the arithmetic yourself.
|
|
51
52
|
|
|
52
53
|
A push-in makes the word smaller at the start in proportion: from z 9 to z 6 it begins at two thirds of its final width. A start at about 25 percent further away than the end (z 7.5 to 6) reads as a gentle push; a larger one begins as a small word. Camera sideways moves (`x`) are not counted, so keep the word near the middle when you orbit.
|
|
53
54
|
|
|
@@ -83,7 +84,7 @@ A word and a ring of labelled cards on a phone frame. Nothing is sized by hand,
|
|
|
83
84
|
|
|
84
85
|
```tsx
|
|
85
86
|
import React from "react";
|
|
86
|
-
import { AbsoluteFill } from "
|
|
87
|
+
import { AbsoluteFill } from "reelkit/frame";
|
|
87
88
|
import { Card3D, Orbit3D, Scene3D, SceneFrame, Text3D, palettes } from "reelkit/kit";
|
|
88
89
|
import type { VideoProps } from "reelkit/kit";
|
|
89
90
|
|
|
@@ -114,7 +115,7 @@ The name arrives as blocks that fly out of a cloud and settle into the letters,
|
|
|
114
115
|
|
|
115
116
|
```tsx
|
|
116
117
|
import React from "react";
|
|
117
|
-
import { AbsoluteFill } from "
|
|
118
|
+
import { AbsoluteFill } from "reelkit/frame";
|
|
118
119
|
import { Assemble3D, Scene3D, SceneFrame, palettes } from "reelkit/kit";
|
|
119
120
|
import type { VideoProps } from "reelkit/kit";
|
|
120
121
|
|
|
@@ -52,7 +52,7 @@ The composition. The number finishes counting as "thousand" is said, and each ro
|
|
|
52
52
|
|
|
53
53
|
```tsx
|
|
54
54
|
import React from "react";
|
|
55
|
-
import { AbsoluteFill, Sequence, useVideoConfig } from "
|
|
55
|
+
import { AbsoluteFill, Sequence, useVideoConfig } from "reelkit/frame";
|
|
56
56
|
import { Captions, Counter, Entrance, Music, onWord, SceneFrame, sceneById, Sfx, Voiceover, wordFrame } from "reelkit/kit";
|
|
57
57
|
import type { VideoProps } from "reelkit/kit";
|
|
58
58
|
|
package/src/commands/assets.ts
CHANGED
|
@@ -9,13 +9,14 @@ import { GreenTooDullError, keyGreen } from "../project/chromakey";
|
|
|
9
9
|
import { buildManifest, sentenceGaps, voiceoverStale, type ClipRecord, type Voiceover } from "../project/manifest";
|
|
10
10
|
import { loadPlan } from "./plan";
|
|
11
11
|
import { FILE_NAME } from "../render/validate";
|
|
12
|
+
import { migrateFrameImports } from "../render/static-check";
|
|
12
13
|
import { levelOf } from "../project/loudness";
|
|
13
14
|
import { beatReport, measureMusic, type MusicRecord } from "../project/music";
|
|
14
15
|
import { audioDuration } from "../project/refmeasure";
|
|
15
16
|
import { BACKGROUND_CLIP_SENTENCE, BACKGROUND_IMAGE_SENTENCE, isVideoPath, lookFromNotes, readBackground, setBackground } from "../project/background";
|
|
16
17
|
import { probeFile } from "../project/probe";
|
|
17
18
|
import { frameWithAlpha, measureTextZone, stillToVideo, tempDir, type LayerEntry } from "../project/layers";
|
|
18
|
-
import { coverageVerdict } from "../
|
|
19
|
+
import { coverageVerdict } from "../hyperframes/kit/image-layers-math";
|
|
19
20
|
import { svgProblem } from "../project/svgcheck";
|
|
20
21
|
import { FILES, isPrivateProject, openProject, type Project } from "../project/project";
|
|
21
22
|
|
|
@@ -296,6 +297,9 @@ export async function assetsPull(ctx: Ctx, id: string, opts: { scene?: string; f
|
|
|
296
297
|
const path = `src/${pulled.filename}`;
|
|
297
298
|
if (project.exists(path) && !opts.force) return { ok: false, summary: `${path} already exists. Use --force to replace it with the library version.` };
|
|
298
299
|
await download(pulled.url, project.path(path));
|
|
300
|
+
const source = readFileSync(project.path(path), "utf8");
|
|
301
|
+
const migrated = migrateFrameImports(source);
|
|
302
|
+
if (migrated !== source) writeFileSync(project.path(path), migrated);
|
|
299
303
|
recordPull(project, id, { path, kind: "component", title: pulled.item.title, meta: pulled.item.meta });
|
|
300
304
|
const name = pulled.filename.replace(/\.tsx$/, "");
|
|
301
305
|
const example = typeof pulled.item.meta.example === "string" ? `\nExample: ${pulled.item.meta.example}` : "";
|
package/src/contract/index.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// - A library item `id` (pull, commit) matches `^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$`; a `deviceCode` is 1 to 200 characters of plain text.
|
|
15
15
|
// - A `voiceId` is 1 to 64 letters and digits and must be one of the voices the server lists (`voices`), otherwise 400 invalid_request.
|
|
16
16
|
// - A user's component is uploaded for review through the same two calls as any other file (`kind: "component"`). The server checks its source
|
|
17
|
-
// at commit with the same static check a pulled component gets (only react,
|
|
17
|
+
// at commit with the same static check a pulled component gets (only react, reelkit/frame and reelkit/kit imports, no network, no globals that reach
|
|
18
18
|
// outside the video) and refuses a source that fails it with 400 `invalid_request` and the first problem. It stays in `review`, is never found
|
|
19
19
|
// by search or the public listing, and is published only by the owner. A component upload is: `contentType` `text/plain`, `bytes` up to 65,536,
|
|
20
20
|
// `shareable` true, `filename` matching `^[A-Z][A-Za-z0-9]*\.tsx$`, a `description` of at least 20 characters, and `meta.example` a string of at most
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { mountVideo, mountVideo as makeRoot } from "./host";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { delayRender, continueRender } from "./frame";
|
|
2
|
+
const loaded = new Set<string>();
|
|
3
|
+
export function loadFont(family: string, slug: string) {
|
|
4
|
+
if (typeof document !== "undefined" && !loaded.has(slug)) {
|
|
5
|
+
loaded.add(slug);
|
|
6
|
+
const handle = delayRender(`font ${family}`);
|
|
7
|
+
const link = document.createElement("link");
|
|
8
|
+
link.onload = () => continueRender(handle);
|
|
9
|
+
link.onerror = () => { continueRender(handle); throw new Error(`Font stylesheet failed: ${slug}`); };
|
|
10
|
+
link.rel = "stylesheet"; link.href = `./fonts/${slug}.css`; document.head.appendChild(link);
|
|
11
|
+
}
|
|
12
|
+
return { fontFamily: `"${family}"` };
|
|
13
|
+
}
|
|
14
|
+
export const fontFamilies = {
|
|
15
|
+
"Inter": "inter",
|
|
16
|
+
"Sora": "sora",
|
|
17
|
+
"ArchivoBlack": "archivo-black",
|
|
18
|
+
"BebasNeue": "bebas-neue",
|
|
19
|
+
"GreatVibes": "great-vibes",
|
|
20
|
+
"JetBrainsMono": "jetbrains-mono",
|
|
21
|
+
"Kanit": "kanit",
|
|
22
|
+
"Modak": "modak",
|
|
23
|
+
"Montserrat": "montserrat",
|
|
24
|
+
"Roboto": "roboto",
|
|
25
|
+
"RobotoSlab": "roboto-slab",
|
|
26
|
+
"Sacramento": "sacramento",
|
|
27
|
+
"Unbounded": "unbounded",
|
|
28
|
+
"BungeeShade": "bungee-shade",
|
|
29
|
+
"Cinzel": "cinzel",
|
|
30
|
+
"Creepster": "creepster",
|
|
31
|
+
"Heebo": "heebo",
|
|
32
|
+
"KaushanScript": "kaushan-script",
|
|
33
|
+
"LilitaOne": "lilita-one",
|
|
34
|
+
"MrsSaintDelafield": "mrs-saint-delafield",
|
|
35
|
+
"Orbitron": "orbitron",
|
|
36
|
+
"Outfit": "outfit",
|
|
37
|
+
"PermanentMarker": "permanent-marker",
|
|
38
|
+
"PlayfairDisplay": "playfair-display",
|
|
39
|
+
"Rubik": "rubik",
|
|
40
|
+
"Rye": "rye",
|
|
41
|
+
"Syne": "syne",
|
|
42
|
+
"Yellowtail": "yellowtail",
|
|
43
|
+
"Alef": "alef",
|
|
44
|
+
"AmaticSC": "amatic-sc",
|
|
45
|
+
"Assistant": "assistant",
|
|
46
|
+
"DavidLibre": "david-libre",
|
|
47
|
+
"FrankRuhlLibre": "frank-ruhl-libre",
|
|
48
|
+
"Fredoka": "fredoka",
|
|
49
|
+
"Karantina": "karantina",
|
|
50
|
+
"NotoSansHebrew": "noto-sans-hebrew",
|
|
51
|
+
"SecularOne": "secular-one",
|
|
52
|
+
"SuezOne": "suez-one",
|
|
53
|
+
"VarelaRound": "varela-round"
|
|
54
|
+
} as const;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React, { createContext, useContext, useId } from "react";
|
|
2
|
+
export * from "./math";
|
|
3
|
+
export type VideoConfig = { width: number; height: number; fps: number; durationInFrames: number; id?: string };
|
|
4
|
+
export type FrameState = { frame: number; offset: number; duration: number; config: VideoConfig; loop?: number; collecting?: boolean };
|
|
5
|
+
export const FrameContext = createContext<FrameState>({ frame: 0, offset: 0, duration: 1, config: { width: 1080, height: 1920, fps: 30, durationInFrames: 1 } });
|
|
6
|
+
export const useCurrentFrame = () => useContext(FrameContext).frame;
|
|
7
|
+
export const useVideoConfig = () => { const s = useContext(FrameContext); return { ...s.config, durationInFrames: s.duration }; };
|
|
8
|
+
export const AbsoluteFill = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ style, ...props }, ref) => <div ref={ref} {...props} style={{ position: "absolute", inset: 0, display: "flex", flexDirection: "column", ...style }} />);
|
|
9
|
+
export const Img = React.forwardRef<HTMLImageElement, React.ImgHTMLAttributes<HTMLImageElement>>((props, ref) => <img {...props} ref={ref} />);
|
|
10
|
+
export const Sequence: React.FC<{ from?: number; durationInFrames?: number; layout?: "none" | "absolute-fill"; children?: React.ReactNode; name?: string; style?: React.CSSProperties; premountFor?: number }> = ({ from = 0, durationInFrames, layout, children, style }) => {
|
|
11
|
+
const s = useContext(FrameContext), duration = Math.min(durationInFrames ?? s.duration - from, s.duration - from);
|
|
12
|
+
if (!s.collecting && (s.frame < from || s.frame >= from + duration)) return null;
|
|
13
|
+
const content = <FrameContext.Provider value={{ ...s, frame: s.frame - from, offset: s.offset + from, duration }}>{children}</FrameContext.Provider>;
|
|
14
|
+
return layout === "none" ? content : <AbsoluteFill style={style}>{content}</AbsoluteFill>;
|
|
15
|
+
};
|
|
16
|
+
export const Freeze: React.FC<{ frame: number; children?: React.ReactNode }> = ({ frame, children }) => { const s = useContext(FrameContext); return <FrameContext.Provider value={{ ...s, frame }}>{children}</FrameContext.Provider>; };
|
|
17
|
+
export const Loop: React.FC<{ durationInFrames: number; children?: React.ReactNode; times?: number; layout?: "none" | "absolute-fill" }> = ({ durationInFrames, children }) => {
|
|
18
|
+
const s = useContext(FrameContext); return <FrameContext.Provider value={{ ...s, frame: ((s.frame % durationInFrames) + durationInFrames) % durationInFrames, loop: durationInFrames }}>{children}</FrameContext.Provider>;
|
|
19
|
+
};
|
|
20
|
+
export type AudioRecord = { id: string; src: string; start: number; end: number; mediaStart: number; layer: number; volume: number; volumeKeyframes: { time: number; volume: number }[]; type: "audio" | "video"; loop: boolean };
|
|
21
|
+
export const audioRecords = new Map<string, AudioRecord>();
|
|
22
|
+
type MediaProps = { src: string; volume?: number | ((frame: number) => number); startFrom?: number; endAt?: number; trimBefore?: number; trimAfter?: number; playbackRate?: number; loopVolumeCurveBehavior?: "extend" | "repeat" };
|
|
23
|
+
function recordAudio(id: string, props: MediaProps & { loop?: boolean }, type: "audio" | "video", state: FrameState) {
|
|
24
|
+
const { config: { fps } } = state;
|
|
25
|
+
const volume = typeof props.volume === "function" ? props.volume(state.frame) : props.volume ?? 1;
|
|
26
|
+
const start = state.offset / fps, end = Math.min(state.config.durationInFrames, state.offset + state.duration) / fps;
|
|
27
|
+
let record = audioRecords.get(id);
|
|
28
|
+
if (!record) { record = { id, src: props.src, start, end, mediaStart: (props.trimBefore ?? props.startFrom ?? 0) / fps, layer: audioRecords.size, volume, volumeKeyframes: [], type, loop: props.loop ?? false }; audioRecords.set(id, record); }
|
|
29
|
+
const time = (state.frame + state.offset) / fps;
|
|
30
|
+
if (time >= start && time < end && typeof props.volume === "function") record.volumeKeyframes.push({ time, volume });
|
|
31
|
+
}
|
|
32
|
+
export const Audio: React.FC<MediaProps & { loop?: boolean }> = (props) => {
|
|
33
|
+
const s = useContext(FrameContext), id = useId(); recordAudio(id, props, "audio", s);
|
|
34
|
+
return <audio id={`hf-audio-${id.replace(/[^a-z0-9]/gi, "")}`} src={props.src} preload="none" data-start={s.offset / s.config.fps} data-duration={s.duration / s.config.fps} />;
|
|
35
|
+
};
|
|
36
|
+
export const OffthreadVideo: React.FC<Omit<React.VideoHTMLAttributes<HTMLVideoElement>, "volume"> & MediaProps & { transparent?: boolean; pauseWhenBuffering?: boolean }> = ({ volume, startFrom, endAt, trimBefore, trimAfter, transparent, pauseWhenBuffering, playbackRate = 1, ...props }) => {
|
|
37
|
+
const s = useContext(FrameContext), id = useId();
|
|
38
|
+
if (!props.muted) recordAudio(id, { ...props, src: props.src!, volume, startFrom, trimBefore }, "video", s);
|
|
39
|
+
return <video {...props} muted playsInline preload="auto" data-hf-frame-video="true" data-hf-time={Math.max(0, s.frame / s.config.fps * playbackRate + (trimBefore ?? startFrom ?? 0) / s.config.fps)} data-hf-loop={props.loop || s.loop ? "true" : undefined} />;
|
|
40
|
+
};
|
|
41
|
+
export const Video = OffthreadVideo;
|
|
42
|
+
let nextHandle = 0;
|
|
43
|
+
const pending = new Map<number, { promise: Promise<void>; resolve: () => void }>();
|
|
44
|
+
export function delayRender(_label?: string): number { const id = ++nextHandle; let resolve!: () => void; const promise = new Promise<void>((r) => { resolve = r; }); pending.set(id, { promise, resolve }); return id; }
|
|
45
|
+
export function continueRender(handle: number): void { pending.get(handle)?.resolve(); pending.delete(handle); }
|
|
46
|
+
export async function waitForRender(): Promise<void> { while (pending.size) await Promise.all([...pending.values()].map((p) => p.promise)); }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { HfProtocol } from "@hyperframes/engine";
|
|
3
|
+
import { createRoot } from "react-dom/client";
|
|
4
|
+
import { flushSync } from "react-dom";
|
|
5
|
+
import { audioRecords, FrameContext, waitForRender } from "./frame";
|
|
6
|
+
import { ManifestContext, UrlsContext } from "./kit/media";
|
|
7
|
+
import type { VideoProps } from "./types";
|
|
8
|
+
|
|
9
|
+
declare global { interface Window { __reelkitProps: VideoProps; __reelkitReady: Promise<void>; __reelkitAudio: typeof audioRecords; __hf?: HfProtocol; } }
|
|
10
|
+
async function settleMedia() {
|
|
11
|
+
await waitForRender();
|
|
12
|
+
await document.fonts.ready;
|
|
13
|
+
await Promise.all([...document.images].map(async (img) => { if (!img.complete) await new Promise<void>((resolve, reject) => { img.addEventListener("load", () => resolve(), { once: true }); img.addEventListener("error", () => reject(new Error(`Image failed: ${img.src}`)), { once: true }); }); if (img.naturalWidth === 0) throw new Error(`Image failed: ${img.src}`); await img.decode(); }));
|
|
14
|
+
await Promise.all([...document.querySelectorAll<HTMLVideoElement>("video[data-hf-frame-video]")].map(async (video) => {
|
|
15
|
+
if (video.readyState < 1) await new Promise<void>((resolve, reject) => { video.addEventListener("loadedmetadata", () => resolve(), { once: true }); video.addEventListener("error", () => reject(new Error(`Video failed: ${video.src}`)), { once: true }); });
|
|
16
|
+
const time = Number(video.dataset.hfTime), duration = video.duration;
|
|
17
|
+
const target = video.dataset.hfLoop ? time % duration : Math.min(time, Math.max(0, duration - 0.00001));
|
|
18
|
+
if (video.readyState < 2 || Math.abs(video.currentTime - target) > 0.00001) await new Promise<void>((resolve, reject) => { video.addEventListener("seeked", () => resolve(), { once: true }); video.addEventListener("error", () => reject(new Error(`Video seek failed: ${video.src}`)), { once: true }); video.currentTime = target; });
|
|
19
|
+
video.pause();
|
|
20
|
+
}));
|
|
21
|
+
await new Promise<void>((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
|
|
22
|
+
}
|
|
23
|
+
export function mountVideo(Video: React.ComponentType<VideoProps>, previewProps?: VideoProps) {
|
|
24
|
+
const props = previewProps ?? window.__reelkitProps, m = props.manifest;
|
|
25
|
+
const stage = document.getElementById("stage")!;
|
|
26
|
+
stage.style.width = `${m.width}px`; stage.style.height = `${m.height}px`;
|
|
27
|
+
stage.dataset.compositionId = "Video"; stage.dataset.noTimeline = "true"; stage.dataset.duration = String(m.totalFrames / m.fps);
|
|
28
|
+
stage.dataset.width = String(m.width); stage.dataset.height = String(m.height);
|
|
29
|
+
const root = createRoot(stage);
|
|
30
|
+
window.__reelkitAudio = audioRecords;
|
|
31
|
+
const seek = (time: number) => {
|
|
32
|
+
const frame = Math.min(m.totalFrames - 1, Math.max(0, Math.round(time * m.fps)));
|
|
33
|
+
flushSync(() => root.render(<FrameContext.Provider value={{ frame, offset: 0, duration: m.totalFrames, config: { width: m.width, height: m.height, fps: m.fps, durationInFrames: m.totalFrames } }}><UrlsContext.Provider value={props.urls}><ManifestContext.Provider value={m}><Video {...props} /></ManifestContext.Provider></UrlsContext.Provider></FrameContext.Provider>));
|
|
34
|
+
window.__reelkitReady = settleMedia();
|
|
35
|
+
};
|
|
36
|
+
window.__hf = { duration: m.totalFrames / m.fps, seek };
|
|
37
|
+
seek(0);
|
|
38
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import { Color, Object3D, type InstancedMesh } from "three";
|
|
4
4
|
import { CameraKeysContext } from "./scene3d-context";
|
|
5
5
|
import { hash01 } from "./seeded";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
|
+
import { brandTransformAt, type BrandTransform, type BrandTransformTiming } from "./brand-transform";
|
|
4
|
+
|
|
5
|
+
// A branded object turning into view, arriving on an arc, or launching from depth. Use inside Scene3D around a Card3D, Hero3D or Text3D.
|
|
6
|
+
// The final pose holds exactly: the real logo or product can be read without a continuous spin. Sound stays outside Scene3D; brandTransformFrames gives its landing frame.
|
|
7
|
+
export const BrandTransform3D: React.FC<BrandTransformTiming & { mode?: BrandTransform; strength?: number; children?: React.ReactNode }> = ({ children, ...options }) => {
|
|
8
|
+
const frame = useCurrentFrame(), { fps } = useVideoConfig();
|
|
9
|
+
const pose = brandTransformAt(frame, fps, options);
|
|
10
|
+
if (!pose.visible) return null;
|
|
11
|
+
return <group position={pose.position} rotation={pose.rotation} scale={pose.scale}>{children}</group>;
|
|
12
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { continueRender, delayRender, interpolate, spring, useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { continueRender, delayRender, interpolate, spring, useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import { Place } from "./Place";
|
|
4
4
|
import { useMedia } from "./media";
|
|
5
5
|
import { fonts } from "./theme";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import { applyPunches, cameraAt, cameraTransform, type CameraKey, type Punch } from "./motion-math";
|
|
4
4
|
|
|
5
5
|
// Moves the whole picture. Wrap all the scenes in it for one camera that never cuts, or wrap one scene's content.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import type { WordTiming } from "../../pipeline/schema";
|
|
4
4
|
import { captionGroups, DEFAULT_GROUP_WORDS, groupAt, type CaptionGroup } from "./caption-groups";
|
|
5
5
|
import { inkOnAccent, legibleAccent, PLATE_COLOR } from "./caption-style";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import { useLoader } from "@react-three/fiber";
|
|
4
4
|
import { CanvasTexture, FrontSide, Shape, ShapeGeometry, SRGBColorSpace, TextureLoader, type Texture } from "three";
|
|
5
|
-
import { delayRender, continueRender } from "
|
|
5
|
+
import { delayRender, continueRender } from "../frame";
|
|
6
6
|
import { useMedia } from "./media";
|
|
7
7
|
import { fonts } from "./theme";
|
|
8
8
|
import { degToRad, floatAt, inkOn, mixHex, type Vec3 } from "./three-math";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import { boxAt, type Box, type BoxKey } from "./motion-math";
|
|
4
4
|
|
|
5
5
|
// What a function child is given: the box now, as fractions of the frame, and its size in pixels.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import { font as loadFont, fonts, springs } from "./theme";
|
|
4
4
|
import { inkOn, mixHex } from "./three-math";
|
|
5
5
|
import { chapterFrames, clamp01 } from "./ui-math";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { useMedia } from "./media";
|
|
3
|
-
import { AbsoluteFill, OffthreadVideo } from "
|
|
3
|
+
import { AbsoluteFill, OffthreadVideo } from "../frame";
|
|
4
4
|
|
|
5
5
|
// A generated or library video clip as a scene's picture. Place it inside the scene's SceneFrame, as the bottom layer, so it starts with the scene.
|
|
6
6
|
// When the scene runs longer than the clip, the clip holds its last frame (it does not loop: a looped person or animal jumps back and shows the seam).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { fonts } from "./theme";
|
|
3
|
-
import { AbsoluteFill, interpolate, useCurrentFrame, useVideoConfig } from "
|
|
3
|
+
import { AbsoluteFill, interpolate, useCurrentFrame, useVideoConfig } from "../frame";
|
|
4
4
|
|
|
5
5
|
export const Counter: React.FC<{ from?: number; to: number; durationFrames?: number; prefix?: string; suffix?: string; color?: string }> = ({
|
|
6
6
|
from = 0, to, durationFrames = 30, prefix = "", suffix = "", color = "#ffffff",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { interpolate, useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { interpolate, useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import { Place } from "./Place";
|
|
4
4
|
import { fonts } from "./theme";
|
|
5
5
|
import { DEFAULT_HERO } from "./ui-theme";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { interpolate, spring, useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { interpolate, spring, useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import { ease, springs } from "./theme";
|
|
4
4
|
|
|
5
5
|
// The workhorse entrance: fade + rise + scale together, with an optional faster exit and a slow idle "breathe".
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { useMedia } from "./media";
|
|
3
|
-
import { AbsoluteFill, OffthreadVideo } from "
|
|
3
|
+
import { AbsoluteFill, OffthreadVideo } from "../frame";
|
|
4
4
|
|
|
5
5
|
export const FootageLayer: React.FC<{ src: string; muted?: boolean; dim?: number }> = ({ src, muted = true, dim = 0 }) => (
|
|
6
6
|
<AbsoluteFill>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { spring, useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { spring, useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import { Place } from "./Place";
|
|
4
4
|
import { DEFAULT_HERO, rgba, uiColors, type UiTheme } from "./ui-theme";
|
|
5
5
|
import { PANEL_SPRING } from "./ui-math";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { AbsoluteFill, Img, OffthreadVideo, Sequence, useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { AbsoluteFill, Img, OffthreadVideo, Sequence, useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import { auroraBlobs, beamStates, breath, checkItems, grainSeed, gridOffset, gridRows, groundStrength, hueShift, mixHex, plateView, withAlpha, sequenceLayers, videoLayers, type Drift, type SequenceItem, type SequenceTransition } from "./bg-math";
|
|
4
4
|
import { useMedia } from "./media";
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { continueRender, delayRender, spring, useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { continueRender, delayRender, spring, useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import { Place } from "./Place";
|
|
4
4
|
import { fonts, springs } from "./theme";
|
|
5
5
|
import { inkOn } from "./three-math";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { continueRender, delayRender, useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { continueRender, delayRender, useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import { useThree } from "@react-three/fiber";
|
|
4
4
|
import { CanvasTexture, DoubleSide, ExtrudeGeometry, Shape, ShapeGeometry, SRGBColorSpace, TextureLoader, type BufferGeometry, type Texture } from "three";
|
|
5
5
|
import { RoundedBoxGeometry } from "three/examples/jsm/geometries/RoundedBoxGeometry.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import { font as loadFont } from "./theme";
|
|
4
4
|
import { rgba } from "./ui-theme";
|
|
5
5
|
import { timecode } from "./ui-math";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import { useMedia } from "./media";
|
|
4
4
|
import { layerMoves, type ImageLayersRecord } from "./image-layers-math";
|
|
5
5
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { useMedia } from "./media";
|
|
3
3
|
import { ease } from "./theme";
|
|
4
|
-
import { AbsoluteFill, Img, interpolate, useCurrentFrame, useVideoConfig } from "
|
|
4
|
+
import { AbsoluteFill, Img, interpolate, useCurrentFrame, useVideoConfig } from "../frame";
|
|
5
5
|
|
|
6
6
|
// direction "out" starts zoomed and pulls back; alternate between consecutive image scenes.
|
|
7
7
|
export const KenBurnsImage: React.FC<{ src: string; zoom?: number; direction?: "in" | "out" }> = ({ src, zoom = 1.1, direction = "in" }) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { useMedia } from "./media";
|
|
3
|
-
import { AbsoluteFill, OffthreadVideo } from "
|
|
3
|
+
import { AbsoluteFill, OffthreadVideo } from "../frame";
|
|
4
4
|
|
|
5
5
|
// A green-screen clip with its green keyed out (the transparent .webm that `--green` makes), laid over whatever is beneath it.
|
|
6
6
|
// x and y are where the clip's bottom-centre sits, as fractions of the frame (x 0 is the left edge, y 1 the bottom edge); scale is the clip's
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
|
|
4
4
|
// The bottom layer of every scene: the base colour with two slow-drifting soft colour fields. Never a flat fill.
|
|
5
5
|
export const BgMesh: React.FC<{ bg: string; hero: string; accent?: string }> = ({ bg, hero, accent = hero }) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { fonts } from "./theme";
|
|
3
|
-
import { AbsoluteFill, spring, useCurrentFrame, useVideoConfig } from "
|
|
3
|
+
import { AbsoluteFill, spring, useCurrentFrame, useVideoConfig } from "../frame";
|
|
4
4
|
|
|
5
5
|
export const LowerThird: React.FC<{ title: string; subtitle?: string; accent?: string }> = ({ title, subtitle, accent = "#5b8cff" }) => {
|
|
6
6
|
const frame = useCurrentFrame();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Audio, useVideoConfig } from "
|
|
2
|
+
import { Audio, useVideoConfig } from "../frame";
|
|
3
3
|
import { DEFAULT_DUCK_TO, DEFAULT_MUSIC_VOLUME, musicVolume, speechSpans, type MusicDip, type SpeechScene } from "./music-math";
|
|
4
4
|
import { useGain, useManifest, useMedia } from "./media";
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { AbsoluteFill, interpolate, useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { AbsoluteFill, interpolate, useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import { fonts } from "./theme";
|
|
4
4
|
import { inkOn } from "./three-math";
|
|
5
5
|
import { DEFAULT_HERO, rgba } from "./ui-theme";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import { CameraKeysContext } from "./scene3d-context";
|
|
4
4
|
import { naturalRingRadius, ringFitScale, ringPosition, type Vec3 } from "./three-math";
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import { AdditiveBlending, BufferAttribute, CanvasTexture, Color, SRGBColorSpace } from "three";
|
|
4
4
|
import { CameraKeysContext } from "./scene3d-context";
|
|
5
5
|
import { textPoints } from "./sample-text";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { AbsoluteFill, useVideoConfig } from "
|
|
2
|
+
import { AbsoluteFill, useVideoConfig } from "../frame";
|
|
3
3
|
|
|
4
4
|
// Centres a product-surface component in the frame, at fraction `y` of the height (0.5 is the middle). Used inside the components themselves.
|
|
5
5
|
export const Place: React.FC<{ y?: number; children: React.ReactNode }> = ({ y = 0.5, children }) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { interpolate, spring, useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { interpolate, spring, useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import { Place } from "./Place";
|
|
4
4
|
import { fonts, springs } from "./theme";
|
|
5
5
|
import { DEFAULT_HERO, rgba, uiColors, type UiTheme } from "./ui-theme";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { useCurrentFrame, useVideoConfig } from "
|
|
3
|
-
import { ThreeCanvas } from "
|
|
2
|
+
import { useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
|
+
import { ThreeCanvas } from "../three";
|
|
4
4
|
import { useThree } from "@react-three/fiber";
|
|
5
5
|
import { ManifestContext, UrlsContext } from "./media";
|
|
6
6
|
import { camera3DAt, type Camera3DKey } from "./three-math";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { AbsoluteFill, Sequence, useCurrentFrame, useVideoConfig } from "
|
|
2
|
+
import { AbsoluteFill, Sequence, useCurrentFrame, useVideoConfig } from "../frame";
|
|
3
3
|
import { transitionAt, transitionCss, type TransitionKind } from "./transition-math";
|
|
4
4
|
|
|
5
5
|
export type SceneTransition = {
|