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.
Files changed (59) hide show
  1. package/README.md +10 -3
  2. package/assets/audio/PROVENANCE.md +16 -0
  3. package/assets/audio/bright-sparks.mp3 +0 -0
  4. package/assets/audio/calm-keys.mp3 +0 -0
  5. package/assets/audio/circuit-pulse.mp3 +0 -0
  6. package/assets/audio/clean-horizon.mp3 +0 -0
  7. package/assets/audio/generate-tracks.mjs +218 -0
  8. package/assets/audio/manifest.json +83 -0
  9. package/assets/audio/midnight-protocol.mp3 +0 -0
  10. package/assets/audio/pixel-bounce.mp3 +0 -0
  11. package/assets/audio/steady-launch.mp3 +0 -0
  12. package/assets/audio/sunny-loop.mp3 +0 -0
  13. package/assets/audio/vector-grid.mp3 +0 -0
  14. package/package.json +9 -2
  15. package/src/cache.mjs +155 -14
  16. package/src/cli.mjs +20 -11
  17. package/src/render.mjs +172 -9
  18. package/template/package.json +6 -5
  19. package/template/pnpm-lock.yaml +186 -116
  20. package/template/pnpm-workspace.yaml +2 -0
  21. package/template/src/Root.tsx +73 -51
  22. package/template/src/audio.ts +24 -0
  23. package/template/src/benchmark.ts +18 -0
  24. package/template/src/brief.json +7 -6
  25. package/template/src/brief.ts +73 -5
  26. package/template/src/cinematic/Atmosphere.tsx +139 -0
  27. package/template/src/cinematic/Camera.tsx +54 -0
  28. package/template/src/cinematic/look.ts +106 -0
  29. package/template/src/cinematic/transitions.tsx +110 -0
  30. package/template/src/components/primitives/Bar.tsx +86 -0
  31. package/template/src/components/primitives/Caption.tsx +5 -4
  32. package/template/src/components/primitives/Icon.tsx +7 -0
  33. package/template/src/components/primitives/KeyPill.tsx +1 -1
  34. package/template/src/components/primitives/Kicker.tsx +46 -0
  35. package/template/src/components/primitives/Label.tsx +22 -25
  36. package/template/src/components/primitives/Loop.tsx +65 -0
  37. package/template/src/components/primitives/RevealText.tsx +123 -0
  38. package/template/src/components/primitives/Stage.tsx +62 -0
  39. package/template/src/components/primitives/Terminal.tsx +11 -0
  40. package/template/src/components/scenes/Benchmark.tsx +83 -0
  41. package/template/src/components/scenes/BrowserFrame.tsx +5 -4
  42. package/template/src/components/scenes/CTA.tsx +67 -58
  43. package/template/src/components/scenes/Clip.tsx +131 -0
  44. package/template/src/components/scenes/CodeReveal.tsx +10 -8
  45. package/template/src/components/scenes/DataFlow.tsx +5 -4
  46. package/template/src/components/scenes/FeatureList.tsx +80 -78
  47. package/template/src/components/scenes/FileTree.tsx +5 -4
  48. package/template/src/components/scenes/Hook.tsx +55 -0
  49. package/template/src/components/scenes/HotkeyScene.tsx +8 -6
  50. package/template/src/components/scenes/MobileScreen.tsx +308 -155
  51. package/template/src/components/scenes/OSWindowScene.tsx +8 -7
  52. package/template/src/components/scenes/Problem.tsx +29 -30
  53. package/template/src/components/scenes/SplitComparison.tsx +65 -92
  54. package/template/src/components/scenes/StatCallout.tsx +162 -18
  55. package/template/src/components/scenes/TerminalScene.tsx +16 -16
  56. package/template/src/duration.ts +23 -2
  57. package/template/src/index.ts +7 -5
  58. package/template/src/platforms.ts +4 -0
  59. package/template/src/timing.ts +49 -0
@@ -1,110 +1,112 @@
1
1
  import React from "react";
2
- import { AbsoluteFill, useCurrentFrame, useVideoConfig, spring, interpolate } from "remotion";
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 { Caption } from "../primitives/Caption";
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 captionStart = HEADLINE_FRAMES + scene.items.length * FRAMES_PER_ITEM + 20;
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
- <AbsoluteFill
25
- style={{
26
- background: theme.bg,
27
- display: "flex",
28
- flexDirection: "column",
29
- alignItems: "center",
30
- justifyContent: "center",
31
- padding: "0 200px",
32
- }}
33
- >
34
- {scene.headline && (
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
- <div style={{ display: "flex", flexDirection: "column", gap: 28, width: "100%" }}>
41
- {scene.items.map((item, i) => {
42
- const text = typeof item === "string" ? item : item.text;
43
- const icon = typeof item === "string" ? undefined : item.icon;
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
- const itemStart = HEADLINE_FRAMES + i * FRAMES_PER_ITEM;
46
- const progress = spring({ frame: frame - itemStart, fps, config: theme.motion });
47
- const opacity = interpolate(Math.max(0, progress), [0, 1], [0, 1]);
48
- const translateX = interpolate(Math.max(0, progress), [0, 1], [-24, 0]);
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
- return (
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
- justifyContent: "center",
71
- flexShrink: 0,
59
+ gap: 26,
60
+ opacity,
61
+ translate: `${translateX}px 0`,
72
62
  }}
73
63
  >
74
- {icon ? (
75
- <Icon name={icon} size={18} color={theme.accent} />
76
- ) : (
77
- <span
78
- style={{
79
- fontFamily: theme.fontMono,
80
- fontSize: 14,
81
- fontWeight: 700,
82
- color: theme.accent,
83
- lineHeight: 1,
84
- }}
85
- >
86
- {i + 1}
87
- </span>
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
- <span
91
- style={{
92
- fontFamily: theme.fontSans,
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
- {scene.caption && <Caption text={scene.caption} theme={theme} startFrame={captionStart} />}
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: theme.bg,
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
- transform: `translateX(${translateX}px)`,
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 actionStart = scene.keys.length * FRAMES_PER_KEY + ACTION_DELAY;
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: theme.bg,
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
- {scene.keys.map((key, i) => (
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
- transform: `translateY(${actionY}px)`,
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
  };