reelme 0.2.2 → 0.4.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 +10 -3
- package/assets/audio/PROVENANCE.md +16 -0
- package/assets/audio/bright-sparks.mp3 +0 -0
- package/assets/audio/calm-keys.mp3 +0 -0
- package/assets/audio/circuit-pulse.mp3 +0 -0
- package/assets/audio/clean-horizon.mp3 +0 -0
- package/assets/audio/generate-tracks.mjs +218 -0
- package/assets/audio/manifest.json +83 -0
- package/assets/audio/midnight-protocol.mp3 +0 -0
- package/assets/audio/pixel-bounce.mp3 +0 -0
- package/assets/audio/steady-launch.mp3 +0 -0
- package/assets/audio/sunny-loop.mp3 +0 -0
- package/assets/audio/vector-grid.mp3 +0 -0
- package/package.json +9 -2
- package/src/cache.mjs +155 -14
- package/src/cli.mjs +20 -11
- package/src/render.mjs +172 -9
- package/template/package.json +6 -5
- package/template/pnpm-lock.yaml +186 -116
- package/template/pnpm-workspace.yaml +2 -0
- package/template/src/Root.tsx +73 -51
- package/template/src/audio.ts +24 -0
- package/template/src/benchmark.ts +18 -0
- package/template/src/brief.json +7 -6
- package/template/src/brief.ts +73 -5
- package/template/src/cinematic/Atmosphere.tsx +139 -0
- package/template/src/cinematic/Camera.tsx +54 -0
- package/template/src/cinematic/look.ts +106 -0
- package/template/src/cinematic/transitions.tsx +110 -0
- package/template/src/components/primitives/Bar.tsx +86 -0
- package/template/src/components/primitives/Caption.tsx +5 -4
- package/template/src/components/primitives/Icon.tsx +7 -0
- package/template/src/components/primitives/KeyPill.tsx +1 -1
- package/template/src/components/primitives/Kicker.tsx +46 -0
- package/template/src/components/primitives/Label.tsx +22 -25
- package/template/src/components/primitives/Loop.tsx +65 -0
- package/template/src/components/primitives/RevealText.tsx +123 -0
- package/template/src/components/primitives/Stage.tsx +62 -0
- package/template/src/components/primitives/Terminal.tsx +11 -0
- package/template/src/components/scenes/Benchmark.tsx +83 -0
- package/template/src/components/scenes/BrowserFrame.tsx +5 -4
- package/template/src/components/scenes/CTA.tsx +67 -58
- package/template/src/components/scenes/Clip.tsx +131 -0
- package/template/src/components/scenes/CodeReveal.tsx +10 -8
- package/template/src/components/scenes/DataFlow.tsx +5 -4
- package/template/src/components/scenes/FeatureList.tsx +80 -78
- package/template/src/components/scenes/FileTree.tsx +5 -4
- package/template/src/components/scenes/Hook.tsx +55 -0
- package/template/src/components/scenes/HotkeyScene.tsx +8 -6
- package/template/src/components/scenes/MobileScreen.tsx +308 -155
- package/template/src/components/scenes/OSWindowScene.tsx +8 -7
- package/template/src/components/scenes/Problem.tsx +29 -30
- package/template/src/components/scenes/SplitComparison.tsx +65 -92
- package/template/src/components/scenes/StatCallout.tsx +162 -18
- package/template/src/components/scenes/TerminalScene.tsx +16 -16
- package/template/src/duration.ts +23 -2
- package/template/src/index.ts +7 -5
- package/template/src/platforms.ts +4 -0
- package/template/src/timing.ts +49 -0
|
@@ -1,110 +1,112 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { useCurrentFrame, useVideoConfig, spring, interpolate } from "remotion";
|
|
3
3
|
import { FeatureListScene as FeatureListBrief } from "../../brief";
|
|
4
4
|
import { Theme } from "../../theme";
|
|
5
|
+
import { PlatformPreset, typeScale } from "../../platforms";
|
|
5
6
|
import { Label } from "../primitives/Label";
|
|
6
|
-
import {
|
|
7
|
-
import { Icon } from "../primitives/Icon";
|
|
7
|
+
import { Stage } from "../primitives/Stage";
|
|
8
|
+
import { Icon, hasIcon } from "../primitives/Icon";
|
|
8
9
|
|
|
9
10
|
interface Props {
|
|
10
11
|
scene: FeatureListBrief;
|
|
11
12
|
theme: Theme;
|
|
13
|
+
platform?: PlatformPreset;
|
|
14
|
+
bottomInset?: number;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
const HEADLINE_FRAMES = 20;
|
|
15
18
|
const FRAMES_PER_ITEM = 25;
|
|
16
19
|
|
|
17
|
-
export const FeatureList: React.FC<Props> = ({ scene, theme }) => {
|
|
20
|
+
export const FeatureList: React.FC<Props> = ({ scene, theme, platform, bottomInset = 0 }) => {
|
|
18
21
|
const frame = useCurrentFrame();
|
|
19
22
|
const { fps } = useVideoConfig();
|
|
23
|
+
const scale = platform ? typeScale(platform) : 1.0;
|
|
20
24
|
|
|
21
|
-
const
|
|
25
|
+
const items = scene.items ?? [];
|
|
26
|
+
const captionStart = HEADLINE_FRAMES + items.length * FRAMES_PER_ITEM + 20;
|
|
27
|
+
const left = scene.align === "left";
|
|
22
28
|
|
|
23
29
|
return (
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<div style={{ marginBottom: 48 }}>
|
|
36
|
-
<Label text={scene.headline} theme={theme} size="lg" startFrame={0} />
|
|
37
|
-
</div>
|
|
38
|
-
)}
|
|
30
|
+
<Stage theme={theme} caption={scene.caption} captionStart={captionStart} bottomInset={bottomInset}>
|
|
31
|
+
{/* The list is a single block, centered in the frame. Even a short
|
|
32
|
+
left-aligned list then sits in symmetric margins (composed negative
|
|
33
|
+
space) instead of being anchored against one edge with a void opposite.
|
|
34
|
+
Items left-align within the block for a clean reading column. */}
|
|
35
|
+
<div style={{ display: "flex", flexDirection: "column", alignItems: "flex-start" }}>
|
|
36
|
+
{scene.headline && (
|
|
37
|
+
<div style={{ marginBottom: 52, alignSelf: left ? "flex-start" : "center" }}>
|
|
38
|
+
<Label text={scene.headline} theme={theme} size="lg" align={left ? "left" : "center"} startFrame={0} />
|
|
39
|
+
</div>
|
|
40
|
+
)}
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 34 }}>
|
|
43
|
+
{items.map((item, i) => {
|
|
44
|
+
const text = typeof item === "string" ? item : item.text;
|
|
45
|
+
const icon = typeof item === "string" ? undefined : item.icon;
|
|
46
|
+
const showIcon = icon !== undefined && hasIcon(icon);
|
|
44
47
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
const itemStart = HEADLINE_FRAMES + i * FRAMES_PER_ITEM;
|
|
49
|
+
const progress = spring({ frame: frame - itemStart, fps, config: theme.motion });
|
|
50
|
+
const opacity = interpolate(Math.max(0, progress), [0, 1], [0, 1]);
|
|
51
|
+
const translateX = interpolate(Math.max(0, progress), [0, 1], [-24, 0]);
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
<div
|
|
52
|
-
key={i}
|
|
53
|
-
style={{
|
|
54
|
-
display: "flex",
|
|
55
|
-
alignItems: "center",
|
|
56
|
-
gap: 24,
|
|
57
|
-
opacity,
|
|
58
|
-
transform: `translateX(${translateX}px)`,
|
|
59
|
-
}}
|
|
60
|
-
>
|
|
53
|
+
return (
|
|
61
54
|
<div
|
|
55
|
+
key={i}
|
|
62
56
|
style={{
|
|
63
|
-
width: 36,
|
|
64
|
-
height: 36,
|
|
65
|
-
borderRadius: "50%",
|
|
66
|
-
background: theme.accentMuted,
|
|
67
|
-
border: `1.5px solid ${theme.accent}`,
|
|
68
57
|
display: "flex",
|
|
69
58
|
alignItems: "center",
|
|
70
|
-
|
|
71
|
-
|
|
59
|
+
gap: 26,
|
|
60
|
+
opacity,
|
|
61
|
+
translate: `${translateX}px 0`,
|
|
72
62
|
}}
|
|
73
63
|
>
|
|
74
|
-
{
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
{
|
|
87
|
-
|
|
88
|
-
|
|
64
|
+
{/* A bold accent marker carries the weight — no faint bordered
|
|
65
|
+
chip (a web-UI badge pattern that reads as filler on screen). */}
|
|
66
|
+
<div
|
|
67
|
+
style={{
|
|
68
|
+
width: 40,
|
|
69
|
+
display: "flex",
|
|
70
|
+
alignItems: "center",
|
|
71
|
+
justifyContent: "center",
|
|
72
|
+
flexShrink: 0,
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
{showIcon ? (
|
|
76
|
+
<Icon name={icon} size={34} color={theme.accent} />
|
|
77
|
+
) : (
|
|
78
|
+
<span
|
|
79
|
+
style={{
|
|
80
|
+
fontFamily: theme.fontSans,
|
|
81
|
+
fontSize: 30,
|
|
82
|
+
fontWeight: 800,
|
|
83
|
+
color: theme.accent,
|
|
84
|
+
lineHeight: 1,
|
|
85
|
+
letterSpacing: "-0.03em",
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
{i + 1}
|
|
89
|
+
</span>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
<span
|
|
93
|
+
style={{
|
|
94
|
+
fontFamily: theme.fontSans,
|
|
95
|
+
fontSize: 40 * scale,
|
|
96
|
+
fontWeight: 600,
|
|
97
|
+
color: theme.text,
|
|
98
|
+
lineHeight: 1.3,
|
|
99
|
+
letterSpacing: "-0.015em",
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
{text}
|
|
103
|
+
</span>
|
|
89
104
|
</div>
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
fontSize: 28,
|
|
94
|
-
fontWeight: 500,
|
|
95
|
-
color: theme.text,
|
|
96
|
-
lineHeight: 1.4,
|
|
97
|
-
letterSpacing: "-0.01em",
|
|
98
|
-
}}
|
|
99
|
-
>
|
|
100
|
-
{text}
|
|
101
|
-
</span>
|
|
102
|
-
</div>
|
|
103
|
-
);
|
|
104
|
-
})}
|
|
105
|
+
);
|
|
106
|
+
})}
|
|
107
|
+
</div>
|
|
105
108
|
</div>
|
|
106
109
|
|
|
107
|
-
|
|
108
|
-
</AbsoluteFill>
|
|
110
|
+
</Stage>
|
|
109
111
|
);
|
|
110
112
|
};
|
|
@@ -8,6 +8,7 @@ import { Caption } from "../primitives/Caption";
|
|
|
8
8
|
interface Props {
|
|
9
9
|
scene: FileTreeBrief;
|
|
10
10
|
theme: Theme;
|
|
11
|
+
bottomInset?: number;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
const HEADLINE_FRAMES = 20;
|
|
@@ -43,7 +44,7 @@ const FileIcon: React.FC<{ color: string }> = ({ color }) => (
|
|
|
43
44
|
</svg>
|
|
44
45
|
);
|
|
45
46
|
|
|
46
|
-
export const FileTree: React.FC<Props> = ({ scene, theme }) => {
|
|
47
|
+
export const FileTree: React.FC<Props> = ({ scene, theme, bottomInset = 0 }) => {
|
|
47
48
|
const frame = useCurrentFrame();
|
|
48
49
|
const { fps } = useVideoConfig();
|
|
49
50
|
|
|
@@ -52,7 +53,7 @@ export const FileTree: React.FC<Props> = ({ scene, theme }) => {
|
|
|
52
53
|
return (
|
|
53
54
|
<AbsoluteFill
|
|
54
55
|
style={{
|
|
55
|
-
background:
|
|
56
|
+
background: "transparent",
|
|
56
57
|
display: "flex",
|
|
57
58
|
flexDirection: "column",
|
|
58
59
|
alignItems: "center",
|
|
@@ -92,7 +93,7 @@ export const FileTree: React.FC<Props> = ({ scene, theme }) => {
|
|
|
92
93
|
alignItems: "center",
|
|
93
94
|
gap: 10,
|
|
94
95
|
opacity,
|
|
95
|
-
|
|
96
|
+
translate: `${translateX}px 0`,
|
|
96
97
|
paddingTop: isHighlighted ? 6 : 4,
|
|
97
98
|
paddingBottom: isHighlighted ? 6 : 4,
|
|
98
99
|
paddingLeft: depth * INDENT + 8,
|
|
@@ -119,7 +120,7 @@ export const FileTree: React.FC<Props> = ({ scene, theme }) => {
|
|
|
119
120
|
})}
|
|
120
121
|
</div>
|
|
121
122
|
|
|
122
|
-
{scene.caption && <Caption text={scene.caption} theme={theme} startFrame={captionStart} />}
|
|
123
|
+
{scene.caption && <Caption text={scene.caption} theme={theme} startFrame={captionStart} bottomInset={bottomInset} />}
|
|
123
124
|
</AbsoluteFill>
|
|
124
125
|
);
|
|
125
126
|
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { AbsoluteFill, useCurrentFrame, useVideoConfig, spring, interpolate } from "remotion";
|
|
3
|
+
import { HookScene as HookBrief } from "../../brief";
|
|
4
|
+
import { Theme } from "../../theme";
|
|
5
|
+
import { PlatformPreset, typeScale } from "../../platforms";
|
|
6
|
+
import { RevealText } from "../primitives/RevealText";
|
|
7
|
+
import { Kicker } from "../primitives/Kicker";
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
scene: HookBrief;
|
|
11
|
+
theme: Theme;
|
|
12
|
+
platform: PlatformPreset;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const Hook: React.FC<Props> = ({ scene, theme, platform }) => {
|
|
16
|
+
const frame = useCurrentFrame();
|
|
17
|
+
const { fps, width } = useVideoConfig();
|
|
18
|
+
const left = scene.align === "left";
|
|
19
|
+
|
|
20
|
+
const fontSize = width * 0.1 * typeScale(platform);
|
|
21
|
+
// The whole block eases out of a slight over-scale so the words don't just
|
|
22
|
+
// appear — they land. A touch of continuous drift keeps the hold alive.
|
|
23
|
+
const settle = spring({ frame, fps, config: { damping: 26, stiffness: 90, mass: 1 } });
|
|
24
|
+
const scale = interpolate(settle, [0, 1], [1.06, 1]);
|
|
25
|
+
const drift = Math.sin(frame / 40) * 0.4;
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<AbsoluteFill
|
|
29
|
+
style={{
|
|
30
|
+
display: "flex",
|
|
31
|
+
flexDirection: "column",
|
|
32
|
+
alignItems: left ? "flex-start" : "center",
|
|
33
|
+
justifyContent: "center",
|
|
34
|
+
padding: left ? "0 0 0 140px" : "0 80px",
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
<div style={{ transform: `scale(${scale}) translateY(${drift}px)`, transformOrigin: left ? "left center" : "center", willChange: "transform" }}>
|
|
38
|
+
{scene.kicker && <Kicker text={scene.kicker} theme={theme} startFrame={0} align={left ? "left" : "center"} />}
|
|
39
|
+
<RevealText
|
|
40
|
+
text={scene.text}
|
|
41
|
+
theme={theme}
|
|
42
|
+
fontSize={fontSize}
|
|
43
|
+
fontWeight={800}
|
|
44
|
+
emphasis={scene.accent}
|
|
45
|
+
align={left ? "left" : "center"}
|
|
46
|
+
startFrame={scene.kicker ? 6 : 0}
|
|
47
|
+
stagger={3.5}
|
|
48
|
+
letterSpacing="-0.04em"
|
|
49
|
+
lineHeight={1.04}
|
|
50
|
+
maxWidth={width * (left ? 0.72 : 0.84)}
|
|
51
|
+
/>
|
|
52
|
+
</div>
|
|
53
|
+
</AbsoluteFill>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
@@ -8,16 +8,18 @@ import { Caption } from "../primitives/Caption";
|
|
|
8
8
|
interface Props {
|
|
9
9
|
scene: HotkeyBrief;
|
|
10
10
|
theme: Theme;
|
|
11
|
+
bottomInset?: number;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
const FRAMES_PER_KEY = 20;
|
|
14
15
|
const ACTION_DELAY = 15;
|
|
15
16
|
|
|
16
|
-
export const Hotkey: React.FC<Props> = ({ scene, theme }) => {
|
|
17
|
+
export const Hotkey: React.FC<Props> = ({ scene, theme, bottomInset = 0 }) => {
|
|
17
18
|
const frame = useCurrentFrame();
|
|
18
19
|
const { fps } = useVideoConfig();
|
|
19
20
|
|
|
20
|
-
const
|
|
21
|
+
const keys = scene.keys ?? [];
|
|
22
|
+
const actionStart = keys.length * FRAMES_PER_KEY + ACTION_DELAY;
|
|
21
23
|
const captionStart = actionStart + 50;
|
|
22
24
|
|
|
23
25
|
const actionProgress = spring({ frame: frame - actionStart, fps, config: theme.motion });
|
|
@@ -27,7 +29,7 @@ export const Hotkey: React.FC<Props> = ({ scene, theme }) => {
|
|
|
27
29
|
return (
|
|
28
30
|
<AbsoluteFill
|
|
29
31
|
style={{
|
|
30
|
-
background:
|
|
32
|
+
background: "transparent",
|
|
31
33
|
display: "flex",
|
|
32
34
|
flexDirection: "column",
|
|
33
35
|
alignItems: "center",
|
|
@@ -36,7 +38,7 @@ export const Hotkey: React.FC<Props> = ({ scene, theme }) => {
|
|
|
36
38
|
}}
|
|
37
39
|
>
|
|
38
40
|
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
|
39
|
-
{
|
|
41
|
+
{keys.map((key, i) => (
|
|
40
42
|
<React.Fragment key={i}>
|
|
41
43
|
{i > 0 && (
|
|
42
44
|
<span
|
|
@@ -59,7 +61,7 @@ export const Hotkey: React.FC<Props> = ({ scene, theme }) => {
|
|
|
59
61
|
<div
|
|
60
62
|
style={{
|
|
61
63
|
opacity: actionOpacity,
|
|
62
|
-
|
|
64
|
+
translate: `0 ${actionY}px`,
|
|
63
65
|
fontFamily: theme.fontSans,
|
|
64
66
|
fontSize: 28,
|
|
65
67
|
fontWeight: 400,
|
|
@@ -71,7 +73,7 @@ export const Hotkey: React.FC<Props> = ({ scene, theme }) => {
|
|
|
71
73
|
</div>
|
|
72
74
|
)}
|
|
73
75
|
|
|
74
|
-
{scene.caption && <Caption text={scene.caption} theme={theme} startFrame={captionStart} />}
|
|
76
|
+
{scene.caption && <Caption text={scene.caption} theme={theme} startFrame={captionStart} bottomInset={bottomInset} />}
|
|
75
77
|
</AbsoluteFill>
|
|
76
78
|
);
|
|
77
79
|
};
|