reelme 0.3.0 → 0.5.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 +18 -11
- package/assets/audio/generate-sfx.mjs +128 -0
- package/assets/audio/light-steps.mp3 +0 -0
- package/assets/audio/lofi-dusk.mp3 +0 -0
- package/assets/audio/manifest.json +56 -63
- package/assets/audio/neon-pulse.mp3 +0 -0
- package/assets/audio/night-drive.mp3 +0 -0
- package/assets/audio/sfx/SOURCES.md +16 -0
- package/assets/audio/sfx/pop.mp3 +0 -0
- package/assets/audio/sfx/rise.mp3 +0 -0
- package/assets/audio/sfx/whoosh.mp3 +0 -0
- package/assets/audio/sunny-bounce.mp3 +0 -0
- package/assets/audio/warm-memories.mp3 +0 -0
- package/package.json +8 -2
- package/src/cache.mjs +246 -16
- package/src/cli.mjs +20 -11
- package/src/render.mjs +98 -19
- package/template/package.json +6 -6
- package/template/pnpm-lock.yaml +177 -122
- package/template/pnpm-workspace.yaml +2 -0
- package/template/src/Root.tsx +101 -21
- package/template/src/brief.json +6 -6
- package/template/src/brief.ts +39 -6
- package/template/src/cinematic/Atmosphere.tsx +176 -30
- package/template/src/cinematic/Camera.tsx +22 -10
- package/template/src/cinematic/look.ts +51 -10
- package/template/src/cinematic/transitions.tsx +29 -9
- package/template/src/components/primitives/Arrow.tsx +8 -5
- package/template/src/components/primitives/Bar.tsx +6 -2
- package/template/src/components/primitives/Caption.tsx +2 -2
- package/template/src/components/primitives/CodeBlock.tsx +21 -7
- package/template/src/components/primitives/Icon.tsx +7 -0
- package/template/src/components/primitives/KeyPill.tsx +1 -1
- package/template/src/components/primitives/Label.tsx +4 -1
- package/template/src/components/primitives/Loop.tsx +65 -0
- package/template/src/components/primitives/RevealText.tsx +35 -7
- package/template/src/components/primitives/Stage.tsx +62 -0
- package/template/src/components/primitives/Terminal.tsx +24 -7
- package/template/src/components/scenes/BrowserFrame.tsx +13 -6
- package/template/src/components/scenes/CTA.tsx +47 -82
- package/template/src/components/scenes/Clip.tsx +31 -12
- package/template/src/components/scenes/CodeReveal.tsx +12 -13
- package/template/src/components/scenes/DataFlow.tsx +47 -19
- package/template/src/components/scenes/FeatureList.tsx +74 -77
- package/template/src/components/scenes/FileTree.tsx +23 -7
- package/template/src/components/scenes/Hook.tsx +4 -3
- package/template/src/components/scenes/HotkeyScene.tsx +4 -3
- package/template/src/components/scenes/MobileScreen.tsx +225 -118
- package/template/src/components/scenes/OSWindowScene.tsx +5 -5
- package/template/src/components/scenes/Problem.tsx +1 -1
- package/template/src/components/scenes/SplitComparison.tsx +41 -14
- package/template/src/components/scenes/StatCallout.tsx +123 -68
- package/template/src/components/scenes/TerminalScene.tsx +12 -11
- package/template/src/custom-scenes.ts +19 -0
- package/template/src/duration.ts +54 -5
- package/template/src/index.ts +16 -10
- package/template/src/theme.ts +10 -1
- package/template/src/timing.ts +49 -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 +0 -213
- 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
|
@@ -1,11 +1,11 @@
|
|
|
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
5
|
import { PlatformPreset, typeScale } from "../../platforms";
|
|
6
6
|
import { Label } from "../primitives/Label";
|
|
7
|
-
import {
|
|
8
|
-
import { Icon } from "../primitives/Icon";
|
|
7
|
+
import { Stage } from "../primitives/Stage";
|
|
8
|
+
import { Icon, hasIcon } from "../primitives/Icon";
|
|
9
9
|
|
|
10
10
|
interface Props {
|
|
11
11
|
scene: FeatureListBrief;
|
|
@@ -22,94 +22,91 @@ export const FeatureList: React.FC<Props> = ({ scene, theme, platform, bottomIns
|
|
|
22
22
|
const { fps } = useVideoConfig();
|
|
23
23
|
const scale = platform ? typeScale(platform) : 1.0;
|
|
24
24
|
|
|
25
|
-
const
|
|
25
|
+
const items = scene.items ?? [];
|
|
26
|
+
const captionStart = HEADLINE_FRAMES + items.length * FRAMES_PER_ITEM + 20;
|
|
26
27
|
const left = scene.align === "left";
|
|
27
28
|
|
|
28
29
|
return (
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
<div style={{ marginBottom: 48 }}>
|
|
41
|
-
<Label text={scene.headline} theme={theme} size="lg" align={left ? "left" : "center"} startFrame={0} />
|
|
42
|
-
</div>
|
|
43
|
-
)}
|
|
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
|
+
)}
|
|
44
41
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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);
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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]);
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
<div
|
|
57
|
-
key={i}
|
|
58
|
-
style={{
|
|
59
|
-
display: "flex",
|
|
60
|
-
alignItems: "center",
|
|
61
|
-
gap: 24,
|
|
62
|
-
opacity,
|
|
63
|
-
transform: `translateX(${translateX}px)`,
|
|
64
|
-
}}
|
|
65
|
-
>
|
|
53
|
+
return (
|
|
66
54
|
<div
|
|
55
|
+
key={i}
|
|
67
56
|
style={{
|
|
68
|
-
width: 36,
|
|
69
|
-
height: 36,
|
|
70
|
-
borderRadius: "50%",
|
|
71
|
-
background: theme.accentMuted,
|
|
72
|
-
border: `1.5px solid ${theme.accent}`,
|
|
73
57
|
display: "flex",
|
|
74
58
|
alignItems: "center",
|
|
75
|
-
|
|
76
|
-
|
|
59
|
+
gap: 26,
|
|
60
|
+
opacity,
|
|
61
|
+
translate: `${translateX}px 0`,
|
|
77
62
|
}}
|
|
78
63
|
>
|
|
79
|
-
{
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
{
|
|
92
|
-
|
|
93
|
-
|
|
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>
|
|
94
104
|
</div>
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
fontSize: 28 * scale,
|
|
99
|
-
fontWeight: 500,
|
|
100
|
-
color: theme.text,
|
|
101
|
-
lineHeight: 1.4,
|
|
102
|
-
letterSpacing: "-0.01em",
|
|
103
|
-
}}
|
|
104
|
-
>
|
|
105
|
-
{text}
|
|
106
|
-
</span>
|
|
107
|
-
</div>
|
|
108
|
-
);
|
|
109
|
-
})}
|
|
105
|
+
);
|
|
106
|
+
})}
|
|
107
|
+
</div>
|
|
110
108
|
</div>
|
|
111
109
|
|
|
112
|
-
|
|
113
|
-
</AbsoluteFill>
|
|
110
|
+
</Stage>
|
|
114
111
|
);
|
|
115
112
|
};
|
|
@@ -28,7 +28,7 @@ function getName(path: string): string {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const FolderIcon: React.FC<{ color: string }> = ({ color }) => (
|
|
31
|
-
<svg width="
|
|
31
|
+
<svg width="24" height="24" viewBox="0 0 18 18" fill="none">
|
|
32
32
|
<path
|
|
33
33
|
d="M1 4.5C1 3.67 1.67 3 2.5 3H7L9 5h6.5c.83 0 1.5.67 1.5 1.5v7c0 .83-.67 1.5-1.5 1.5h-13C1.67 15 1 14.33 1 13.5v-9z"
|
|
34
34
|
fill={color}
|
|
@@ -38,7 +38,7 @@ const FolderIcon: React.FC<{ color: string }> = ({ color }) => (
|
|
|
38
38
|
);
|
|
39
39
|
|
|
40
40
|
const FileIcon: React.FC<{ color: string }> = ({ color }) => (
|
|
41
|
-
<svg width="
|
|
41
|
+
<svg width="20" height="24" viewBox="0 0 15 18" fill="none">
|
|
42
42
|
<path d="M2 1h8l4 4v12a1 1 0 01-1 1H2a1 1 0 01-1-1V2a1 1 0 011-1z" stroke={color} strokeWidth="1.5" fill="none" />
|
|
43
43
|
<path d="M9 1v5h5" stroke={color} strokeWidth="1.5" />
|
|
44
44
|
</svg>
|
|
@@ -62,12 +62,28 @@ export const FileTree: React.FC<Props> = ({ scene, theme, bottomInset = 0 }) =>
|
|
|
62
62
|
}}
|
|
63
63
|
>
|
|
64
64
|
{scene.headline && (
|
|
65
|
-
<div style={{ marginBottom: 48
|
|
66
|
-
<Label text={scene.headline} theme={theme} size="lg" startFrame={0} align="
|
|
65
|
+
<div style={{ marginBottom: 48 }}>
|
|
66
|
+
<Label text={scene.headline} theme={theme} size="lg" startFrame={0} align="center" />
|
|
67
67
|
</div>
|
|
68
68
|
)}
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
{/* The tree lives in a real panel (solid surface, brand-lit) so it has
|
|
71
|
+
graphic mass — a bare list of faint rows floating on the stage reads
|
|
72
|
+
as a wireframe, not a shot. */}
|
|
73
|
+
<div
|
|
74
|
+
style={{
|
|
75
|
+
display: "flex",
|
|
76
|
+
flexDirection: "column",
|
|
77
|
+
gap: 6,
|
|
78
|
+
minWidth: 560,
|
|
79
|
+
maxWidth: "100%",
|
|
80
|
+
background: theme.surface,
|
|
81
|
+
border: `1px solid ${theme.border}`,
|
|
82
|
+
borderRadius: 16,
|
|
83
|
+
padding: "32px 40px",
|
|
84
|
+
boxShadow: `0 30px 90px -18px ${theme.accent}59, 0 24px 60px rgba(0,0,0,0.45)`,
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
71
87
|
{scene.entries.map((entry, i) => {
|
|
72
88
|
const entryStart = HEADLINE_FRAMES + i * FRAMES_PER_ENTRY;
|
|
73
89
|
const progress = spring({
|
|
@@ -93,7 +109,7 @@ export const FileTree: React.FC<Props> = ({ scene, theme, bottomInset = 0 }) =>
|
|
|
93
109
|
alignItems: "center",
|
|
94
110
|
gap: 10,
|
|
95
111
|
opacity,
|
|
96
|
-
|
|
112
|
+
translate: `${translateX}px 0`,
|
|
97
113
|
paddingTop: isHighlighted ? 6 : 4,
|
|
98
114
|
paddingBottom: isHighlighted ? 6 : 4,
|
|
99
115
|
paddingLeft: depth * INDENT + 8,
|
|
@@ -107,7 +123,7 @@ export const FileTree: React.FC<Props> = ({ scene, theme, bottomInset = 0 }) =>
|
|
|
107
123
|
<span
|
|
108
124
|
style={{
|
|
109
125
|
fontFamily: theme.fontMono,
|
|
110
|
-
fontSize:
|
|
126
|
+
fontSize: 30,
|
|
111
127
|
fontWeight: isDir ? 600 : 400,
|
|
112
128
|
color: textColor,
|
|
113
129
|
letterSpacing: "-0.01em",
|
|
@@ -19,10 +19,11 @@ export const Hook: React.FC<Props> = ({ scene, theme, platform }) => {
|
|
|
19
19
|
|
|
20
20
|
const fontSize = width * 0.1 * typeScale(platform);
|
|
21
21
|
// The whole block eases out of a slight over-scale so the words don't just
|
|
22
|
-
// appear — they land.
|
|
22
|
+
// appear — they land. No continuous drift on the hold: a sub-pixel sine on
|
|
23
|
+
// giant type reads as vibration, not life (gallery feedback); the Atmosphere
|
|
24
|
+
// behind carries the motion instead.
|
|
23
25
|
const settle = spring({ frame, fps, config: { damping: 26, stiffness: 90, mass: 1 } });
|
|
24
26
|
const scale = interpolate(settle, [0, 1], [1.06, 1]);
|
|
25
|
-
const drift = Math.sin(frame / 40) * 0.4;
|
|
26
27
|
|
|
27
28
|
return (
|
|
28
29
|
<AbsoluteFill
|
|
@@ -34,7 +35,7 @@ export const Hook: React.FC<Props> = ({ scene, theme, platform }) => {
|
|
|
34
35
|
padding: left ? "0 0 0 140px" : "0 80px",
|
|
35
36
|
}}
|
|
36
37
|
>
|
|
37
|
-
<div style={{ transform: `scale(${scale})
|
|
38
|
+
<div style={{ transform: `scale(${scale})`, transformOrigin: left ? "left center" : "center", willChange: "transform" }}>
|
|
38
39
|
{scene.kicker && <Kicker text={scene.kicker} theme={theme} startFrame={0} align={left ? "left" : "center"} />}
|
|
39
40
|
<RevealText
|
|
40
41
|
text={scene.text}
|
|
@@ -18,7 +18,8 @@ export const Hotkey: React.FC<Props> = ({ scene, theme, bottomInset = 0 }) => {
|
|
|
18
18
|
const frame = useCurrentFrame();
|
|
19
19
|
const { fps } = useVideoConfig();
|
|
20
20
|
|
|
21
|
-
const
|
|
21
|
+
const keys = scene.keys ?? [];
|
|
22
|
+
const actionStart = keys.length * FRAMES_PER_KEY + ACTION_DELAY;
|
|
22
23
|
const captionStart = actionStart + 50;
|
|
23
24
|
|
|
24
25
|
const actionProgress = spring({ frame: frame - actionStart, fps, config: theme.motion });
|
|
@@ -37,7 +38,7 @@ export const Hotkey: React.FC<Props> = ({ scene, theme, bottomInset = 0 }) => {
|
|
|
37
38
|
}}
|
|
38
39
|
>
|
|
39
40
|
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
|
40
|
-
{
|
|
41
|
+
{keys.map((key, i) => (
|
|
41
42
|
<React.Fragment key={i}>
|
|
42
43
|
{i > 0 && (
|
|
43
44
|
<span
|
|
@@ -60,7 +61,7 @@ export const Hotkey: React.FC<Props> = ({ scene, theme, bottomInset = 0 }) => {
|
|
|
60
61
|
<div
|
|
61
62
|
style={{
|
|
62
63
|
opacity: actionOpacity,
|
|
63
|
-
|
|
64
|
+
translate: `0 ${actionY}px`,
|
|
64
65
|
fontFamily: theme.fontSans,
|
|
65
66
|
fontSize: 28,
|
|
66
67
|
fontWeight: 400,
|