@devinilabs/reelstack 1.2.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 (145) hide show
  1. package/LICENSE +128 -0
  2. package/README.md +125 -0
  3. package/cli/beats.js +124 -0
  4. package/cli/bootstrap.js +124 -0
  5. package/cli/capture.js +34 -0
  6. package/cli/direction.js +114 -0
  7. package/cli/icons.js +49 -0
  8. package/cli/index.js +101 -0
  9. package/cli/init.js +253 -0
  10. package/cli/license.js +168 -0
  11. package/cli/lint.js +865 -0
  12. package/cli/preview.js +59 -0
  13. package/cli/render.js +404 -0
  14. package/cli/scaffold.js +239 -0
  15. package/cli/smoke.js +76 -0
  16. package/cli/update.js +26 -0
  17. package/cli/utils.js +184 -0
  18. package/docs/buyers-guide.md +220 -0
  19. package/docs/design-discipline.md +130 -0
  20. package/docs/family-galleries/dark.md +95 -0
  21. package/docs/family-galleries/forbidden.md +78 -0
  22. package/docs/family-galleries/glass.md +98 -0
  23. package/docs/family-galleries/paper.md +82 -0
  24. package/docs/family-galleries/warm.md +86 -0
  25. package/docs/superpowers/plans/2026-05-09-reelstack-init-readiness-gate.md +1166 -0
  26. package/docs/superpowers/specs/2026-05-09-reelstack-init-readiness-gate-design.md +233 -0
  27. package/families/dark/components/DriftingSpotlights.tsx +59 -0
  28. package/families/dark/components/FilmGrain.tsx +44 -0
  29. package/families/dark/components/ForestCard.tsx +43 -0
  30. package/families/dark/components/GridBackground.tsx +29 -0
  31. package/families/dark/components/RadialVignette.tsx +21 -0
  32. package/families/dark/components/Scanlines.tsx +35 -0
  33. package/families/dark/components/SegmentOpacity.ts +37 -0
  34. package/families/dark/components/index.ts +13 -0
  35. package/families/dark/index.ts +31 -0
  36. package/families/dark/palette.ts +98 -0
  37. package/families/dark/presets/claudedispatch.ts +46 -0
  38. package/families/dark/presets/codedrop.ts +37 -0
  39. package/families/dark/presets/gpt55.ts +54 -0
  40. package/families/dark/presets/notebooklm.ts +50 -0
  41. package/families/dark/presets/resourcescta.ts +35 -0
  42. package/families/dark/presets/skills.ts +40 -0
  43. package/families/dark/presets/stitch.ts +46 -0
  44. package/families/dark/presets/stitch2.ts +43 -0
  45. package/families/dark/typography.ts +16 -0
  46. package/families/forbidden/components/ForbiddenCausticBlobs.tsx +52 -0
  47. package/families/forbidden/components/NewsprintTexture.tsx +28 -0
  48. package/families/forbidden/components/TintedShadow.tsx +36 -0
  49. package/families/forbidden/components/index.ts +38 -0
  50. package/families/forbidden/index.ts +17 -0
  51. package/families/forbidden/palette.ts +88 -0
  52. package/families/forbidden/presets/heretic.ts +44 -0
  53. package/families/forbidden/typography.ts +18 -0
  54. package/families/glass/components/BreakdownCard.tsx +158 -0
  55. package/families/glass/components/CausticBlobs.tsx +49 -0
  56. package/families/glass/components/Counter.tsx +72 -0
  57. package/families/glass/components/EyebrowPill.tsx +59 -0
  58. package/families/glass/components/FilmStrip.tsx +202 -0
  59. package/families/glass/components/FloatingGlyphs.tsx +78 -0
  60. package/families/glass/components/GlassCard.tsx +58 -0
  61. package/families/glass/components/GlassCardBezel.tsx +45 -0
  62. package/families/glass/components/HairlineGrid.tsx +30 -0
  63. package/families/glass/components/IridescentRing.tsx +114 -0
  64. package/families/glass/components/IridescentText.tsx +98 -0
  65. package/families/glass/components/LightBeam.tsx +46 -0
  66. package/families/glass/components/ParticleBurst.tsx +62 -0
  67. package/families/glass/components/SonarRings.tsx +81 -0
  68. package/families/glass/components/StaggeredWords.tsx +74 -0
  69. package/families/glass/components/index.ts +20 -0
  70. package/families/glass/index.ts +31 -0
  71. package/families/glass/palette.ts +93 -0
  72. package/families/glass/presets/claudewatch.ts +64 -0
  73. package/families/glass/presets/claudewatchcta.ts +43 -0
  74. package/families/glass/presets/graphify.ts +45 -0
  75. package/families/glass/presets/gstack.ts +48 -0
  76. package/families/glass/presets/jcode.ts +50 -0
  77. package/families/glass/presets/lilagents.ts +52 -0
  78. package/families/glass/presets/paperclip.ts +43 -0
  79. package/families/glass/typography.ts +15 -0
  80. package/families/index.ts +49 -0
  81. package/families/paper/components/CardSpring.tsx +42 -0
  82. package/families/paper/components/CreamGrid.tsx +26 -0
  83. package/families/paper/components/EditorialSerifText.tsx +51 -0
  84. package/families/paper/components/GreenAccentCard.tsx +10 -0
  85. package/families/paper/components/PaperShadow.tsx +30 -0
  86. package/families/paper/components/ScaleBlurText.tsx +40 -0
  87. package/families/paper/components/index.ts +11 -0
  88. package/families/paper/index.ts +23 -0
  89. package/families/paper/palette.ts +102 -0
  90. package/families/paper/presets/designreel.ts +32 -0
  91. package/families/paper/presets/devini3d.ts +45 -0
  92. package/families/paper/presets/justdrop.ts +39 -0
  93. package/families/paper/presets/opus.ts +48 -0
  94. package/families/paper/typography.ts +17 -0
  95. package/families/warm/components/AccentGlow.tsx +60 -0
  96. package/families/warm/components/BentoCell.tsx +56 -0
  97. package/families/warm/components/BentoGrid.tsx +30 -0
  98. package/families/warm/components/FilmGrain.tsx +36 -0
  99. package/families/warm/components/ScaleBlurCounter.tsx +71 -0
  100. package/families/warm/components/WarmSurface.tsx +35 -0
  101. package/families/warm/components/index.ts +11 -0
  102. package/families/warm/index.ts +19 -0
  103. package/families/warm/palette.ts +81 -0
  104. package/families/warm/presets/huashu.ts +49 -0
  105. package/families/warm/presets/mempalace.ts +51 -0
  106. package/families/warm/typography.ts +17 -0
  107. package/package.json +85 -0
  108. package/reference/dark/claudedispatch.tsx +2441 -0
  109. package/reference/dark/notebooklm.tsx +2316 -0
  110. package/reference/dark/stitch.tsx +3040 -0
  111. package/reference/forbidden/heretic.tsx +2636 -0
  112. package/reference/glass/claudewatch.tsx +3827 -0
  113. package/reference/glass/graphify.tsx +2418 -0
  114. package/reference/glass/paperclip.tsx +2218 -0
  115. package/reference/paper/designreel.tsx +883 -0
  116. package/reference/paper/justdrop.tsx +1898 -0
  117. package/reference/paper/opus.tsx +1770 -0
  118. package/reference/warm/huashu.tsx +3413 -0
  119. package/reference/warm/mempalace.tsx +2909 -0
  120. package/skill/SKILL.md +229 -0
  121. package/skill/commands/reelstack-beats.md +20 -0
  122. package/skill/commands/reelstack-capture.md +24 -0
  123. package/skill/commands/reelstack-critique.md +15 -0
  124. package/skill/commands/reelstack-dark.md +40 -0
  125. package/skill/commands/reelstack-direction.md +17 -0
  126. package/skill/commands/reelstack-forbidden.md +25 -0
  127. package/skill/commands/reelstack-glass.md +39 -0
  128. package/skill/commands/reelstack-icons.md +22 -0
  129. package/skill/commands/reelstack-init.md +17 -0
  130. package/skill/commands/reelstack-lint.md +22 -0
  131. package/skill/commands/reelstack-paper.md +36 -0
  132. package/skill/commands/reelstack-render.md +20 -0
  133. package/skill/commands/reelstack-warm.md +36 -0
  134. package/templates/dark/template.tsx +115 -0
  135. package/templates/forbidden/template.tsx +111 -0
  136. package/templates/glass/template.tsx +201 -0
  137. package/templates/paper/template.tsx +133 -0
  138. package/templates/warm/template.tsx +210 -0
  139. package/utils/ai-purple-blocklist.ts +13 -0
  140. package/utils/banned-fonts.ts +11 -0
  141. package/utils/cubic-bezier.ts +36 -0
  142. package/utils/easing.ts +84 -0
  143. package/utils/grid.ts +13 -0
  144. package/utils/render-presets.json +56 -0
  145. package/utils/safe-zones.tsx +57 -0
@@ -0,0 +1,2441 @@
1
+ /**
2
+ * REFERENCE — ClaudeDispatchReel (Family: dark)
3
+ *
4
+ * Canonical example of the dark family's motion vocabulary, frame-locked
5
+ * BEAT structure, and scene choreography. Bundled with ReelStack v1.1.1+
6
+ * for STUDY and pattern adaptation.
7
+ *
8
+ * Asset imports stripped (look for REFERENCE-STRIP markers). Bring your own
9
+ * voiceover, brand SVGs, captures.
10
+ *
11
+ * License: study + adapt patterns OK. Verbatim re-publication as your own
12
+ * template NOT OK. See ReelStack LICENSE.
13
+ *
14
+ * Source: my-video/src/ClaudeDispatchReel.tsx
15
+ * Bundled at: 2026-05-08T18:50:39.532Z
16
+ */
17
+ import {
18
+ useCurrentFrame,
19
+ useVideoConfig,
20
+ interpolate,
21
+ spring,
22
+ Easing,
23
+ AbsoluteFill,
24
+ staticFile,
25
+ Sequence,
26
+ OffthreadVideo,
27
+ Img,
28
+ } from "remotion";
29
+ import { Audio } from "@remotion/media";
30
+ import { ds } from "./designSystem";
31
+
32
+ // ═══════════════════════════════════════════════════════════════
33
+ // TOKENS — dark cinematic palette built on the design system
34
+ // ═══════════════════════════════════════════════════════════════
35
+
36
+ const FONT = ds.font.sans;
37
+ const MONO = ds.font.mono;
38
+
39
+ const C = {
40
+ // Base
41
+ bg: "#0a0a0b",
42
+ bgLift: "#141416",
43
+ surface: "#1a1a1d",
44
+ border: "rgba(255,255,255,0.08)",
45
+ borderLoud: "rgba(255,255,255,0.14)",
46
+ // Text
47
+ fg: "#f5f5f7",
48
+ fgSoft: "#d1d1d6",
49
+ fgMuted: "#8e8e93",
50
+ fgDim: "#5a5a60",
51
+ // Brand
52
+ claude: "#D4663A",
53
+ claudeSoft: "#e07a54",
54
+ claudeDim: "rgba(212,102,58,0.15)",
55
+ // Semantic
56
+ danger: "#e25822",
57
+ dangerSoft: "#ff6a45",
58
+ dangerDim: "rgba(226,88,34,0.14)",
59
+ safe: "#4fc46a",
60
+ safeDim: "rgba(79,196,106,0.12)",
61
+ // Accents for cards
62
+ darkGreen: "#1a2e1f",
63
+ darkGreenLift: "#243a28",
64
+ } as const;
65
+
66
+ // Scene timeline — 74s voiceover, 8 segments with crossfade overlaps
67
+ const SEGS = [
68
+ { s: 0, e: 8.2, name: "Hook" },
69
+ { s: 7.8, e: 20, name: "Idea" },
70
+ { s: 19.5, e: 33, name: "Example" },
71
+ { s: 32.5, e: 41, name: "Why" },
72
+ { s: 40.5, e: 52, name: "Issues" },
73
+ { s: 51.5, e: 62.5, name: "Solution" },
74
+ { s: 62, e: 68.5, name: "Wins" },
75
+ { s: 68, e: 74, name: "CTA" },
76
+ ] as const;
77
+
78
+ // ═══════════════════════════════════════════════════════════════
79
+ // UTILITIES
80
+ // ═══════════════════════════════════════════════════════════════
81
+
82
+ // Scene opacity — fade in/out at the edges of each segment
83
+ const so = (
84
+ frame: number,
85
+ fps: number,
86
+ startS: number,
87
+ endS: number,
88
+ fadeIn = 0.45,
89
+ fadeOut = 0.55,
90
+ ) =>
91
+ interpolate(
92
+ frame,
93
+ [startS * fps, (startS + fadeIn) * fps, (endS - fadeOut) * fps, endS * fps],
94
+ [0, 1, 1, 0],
95
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" },
96
+ );
97
+
98
+ // Local frame — elapsed frames within a segment
99
+ const lf = (frame: number, fps: number, startSec: number) =>
100
+ Math.max(0, frame - startSec * fps);
101
+
102
+ // Staggered spring with GSAP-style defaults (bouncy, snappy, gentle)
103
+ const gsapSpring = (
104
+ f: number,
105
+ fps: number,
106
+ delaySec = 0,
107
+ kind: "bouncy" | "snappy" | "gentle" | "glass" = "snappy",
108
+ ) =>
109
+ spring({
110
+ frame: f,
111
+ fps,
112
+ delay: Math.round(delaySec * fps),
113
+ config: ds.spring[kind],
114
+ });
115
+
116
+ // GSAP "back.out(1.7)" approximation — snappy overshoot
117
+ const backOut = (t: number) => {
118
+ const c1 = 1.70158;
119
+ const c3 = c1 + 1;
120
+ return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
121
+ };
122
+
123
+ interface SP {
124
+ frame: number;
125
+ fps: number;
126
+ }
127
+
128
+ // ═══════════════════════════════════════════════════════════════
129
+ // BACKGROUND — dark cinematic with animated grid + orbs
130
+ // ═══════════════════════════════════════════════════════════════
131
+
132
+ const Background: React.FC<{ frame: number; fps: number }> = ({ frame }) => {
133
+ // Slow drifting spotlight
134
+ const drift = (Math.sin(frame * 0.004) + 1) * 0.5;
135
+ const drift2 = (Math.cos(frame * 0.003) + 1) * 0.5;
136
+ const vignettePulse = 0.35 + Math.sin(frame * 0.008) * 0.04;
137
+
138
+ return (
139
+ <AbsoluteFill>
140
+ {/* Base */}
141
+ <div style={{ width: "100%", height: "100%", background: C.bg }} />
142
+
143
+ {/* Primary terracotta spotlight — slowly drifts */}
144
+ <div
145
+ style={{
146
+ position: "absolute",
147
+ inset: 0,
148
+ background: `radial-gradient(ellipse 800px 900px at ${20 + drift * 60}% ${15 + drift2 * 40}%, rgba(212,102,58,0.14) 0%, transparent 60%)`,
149
+ }}
150
+ />
151
+ {/* Cool counter-spot */}
152
+ <div
153
+ style={{
154
+ position: "absolute",
155
+ inset: 0,
156
+ background: `radial-gradient(ellipse 600px 800px at ${80 - drift * 50}% ${70 + drift2 * 25}%, rgba(79,196,106,0.06) 0%, transparent 55%)`,
157
+ }}
158
+ />
159
+
160
+ {/* Grid lines */}
161
+ <div
162
+ style={{
163
+ position: "absolute",
164
+ inset: 0,
165
+ backgroundImage: `
166
+ linear-gradient(rgba(255,255,255,0.035) 1px, transparent 1px),
167
+ linear-gradient(90deg, rgba(255,255,255,0.035) 1px, transparent 1px)
168
+ `,
169
+ backgroundSize: "72px 72px",
170
+ maskImage: "radial-gradient(ellipse at 50% 50%, black 30%, transparent 85%)",
171
+ WebkitMaskImage: "radial-gradient(ellipse at 50% 50%, black 30%, transparent 85%)",
172
+ }}
173
+ />
174
+
175
+ {/* Noise grain — fine texture */}
176
+ <div
177
+ style={{
178
+ position: "absolute",
179
+ inset: 0,
180
+ opacity: 0.06,
181
+ mixBlendMode: "overlay",
182
+ backgroundImage: `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9'/></filter><rect width='200' height='200' filter='url(%23n)' opacity='0.7'/></svg>")`,
183
+ }}
184
+ />
185
+
186
+ {/* Corner vignette */}
187
+ <div
188
+ style={{
189
+ position: "absolute",
190
+ inset: 0,
191
+ background: `radial-gradient(ellipse at 50% 50%, transparent 40%, rgba(0,0,0,${vignettePulse}) 100%)`,
192
+ }}
193
+ />
194
+ </AbsoluteFill>
195
+ );
196
+ };
197
+
198
+ // ═══════════════════════════════════════════════════════════════
199
+ // SAFE ZONE — IG-compliant content container
200
+ // ═══════════════════════════════════════════════════════════════
201
+
202
+ const SafeZone: React.FC<{ children: React.ReactNode; style?: React.CSSProperties }> = ({
203
+ children,
204
+ style,
205
+ }) => (
206
+ <div
207
+ style={{
208
+ position: "absolute",
209
+ top: 290,
210
+ left: 0,
211
+ right: 0,
212
+ bottom: 430,
213
+ ...style,
214
+ }}
215
+ >
216
+ {children}
217
+ </div>
218
+ );
219
+
220
+ // ═══════════════════════════════════════════════════════════════
221
+ // KINETIC TEXT — word-by-word reveal à la GSAP stagger
222
+ // ═══════════════════════════════════════════════════════════════
223
+
224
+ const KineticText: React.FC<{
225
+ text: string;
226
+ f: number;
227
+ fps: number;
228
+ startSec?: number;
229
+ wordStagger?: number;
230
+ size: number;
231
+ weight?: number;
232
+ color?: string;
233
+ mono?: boolean;
234
+ style?: React.CSSProperties;
235
+ }> = ({
236
+ text,
237
+ f,
238
+ fps,
239
+ startSec = 0,
240
+ wordStagger = 0.06,
241
+ size,
242
+ weight = 700,
243
+ color = C.fg,
244
+ mono = false,
245
+ style,
246
+ }) => {
247
+ const words = text.split(" ");
248
+ return (
249
+ <div
250
+ style={{
251
+ display: "flex",
252
+ flexWrap: "wrap",
253
+ gap: "0 0.32em",
254
+ justifyContent: "center",
255
+ fontFamily: mono ? MONO : FONT,
256
+ fontSize: size,
257
+ fontWeight: weight,
258
+ color,
259
+ letterSpacing: "-0.03em",
260
+ lineHeight: 1.04,
261
+ ...style,
262
+ }}
263
+ >
264
+ {words.map((w, i) => {
265
+ const s = gsapSpring(f, fps, startSec + i * wordStagger, "snappy");
266
+ const y = interpolate(s, [0, 1], [40, 0]);
267
+ const op = interpolate(s, [0, 0.4], [0, 1], {
268
+ extrapolateLeft: "clamp",
269
+ extrapolateRight: "clamp",
270
+ });
271
+ return (
272
+ <span
273
+ key={`${w}-${i}`}
274
+ style={{
275
+ display: "inline-block",
276
+ transform: `translateY(${y}px)`,
277
+ opacity: op,
278
+ }}
279
+ >
280
+ {w}
281
+ </span>
282
+ );
283
+ })}
284
+ </div>
285
+ );
286
+ };
287
+
288
+ // ═══════════════════════════════════════════════════════════════
289
+ // SCANLINE — subtle moving highlight line inside cards
290
+ // ═══════════════════════════════════════════════════════════════
291
+
292
+ const Scanline: React.FC<{ frame: number; color?: string }> = ({
293
+ frame,
294
+ color = "rgba(255,255,255,0.09)",
295
+ }) => {
296
+ const y = ((frame * 1.8) % 300) - 50;
297
+ return (
298
+ <div
299
+ style={{
300
+ position: "absolute",
301
+ left: 0,
302
+ right: 0,
303
+ top: y,
304
+ height: 80,
305
+ background: `linear-gradient(180deg, transparent, ${color}, transparent)`,
306
+ pointerEvents: "none",
307
+ }}
308
+ />
309
+ );
310
+ };
311
+
312
+ // ═══════════════════════════════════════════════════════════════
313
+ // SCENE 1 — HOOK (0-8.2s)
314
+ // ═══════════════════════════════════════════════════════════════
315
+
316
+ const HookScene: React.FC<SP> = ({ frame, fps }) => {
317
+ const op = so(frame, fps, SEGS[0].s, SEGS[0].e);
318
+ if (op === 0) return null;
319
+ const f = lf(frame, fps, SEGS[0].s);
320
+
321
+ // Anthropic A logo — subtle pulse in
322
+ const logoSpring = gsapSpring(f, fps, 0, "gentle");
323
+ const logoScale = interpolate(logoSpring, [0, 1], [0.4, 1]);
324
+ const logoOp = interpolate(logoSpring, [0, 0.3], [0, 1], {
325
+ extrapolateLeft: "clamp",
326
+ extrapolateRight: "clamp",
327
+ });
328
+
329
+ // "Anthropic just launched" — kinetic word reveal
330
+ // Primary headline "CLAUDE DISPATCH" — smash in at 2.2s
331
+ const smashSpring = gsapSpring(f, fps, 2.3, "bouncy");
332
+ const smashScale = interpolate(smashSpring, [0, 1], [1.6, 1]);
333
+ const smashOp = interpolate(smashSpring, [0, 0.25], [0, 1], {
334
+ extrapolateLeft: "clamp",
335
+ extrapolateRight: "clamp",
336
+ });
337
+ // Subtle shake at peak of smash
338
+ const shakeT = interpolate(f, [2.3 * fps, 2.7 * fps], [0, 1], {
339
+ extrapolateLeft: "clamp",
340
+ extrapolateRight: "clamp",
341
+ });
342
+ const shakeX = Math.sin(shakeT * 20) * (1 - shakeT) * 6;
343
+
344
+ // OpenClaw fade in at 5s, get crossed out at 6.5s
345
+ const clawSpring = gsapSpring(f, fps, 5, "gentle");
346
+ const clawOp = interpolate(clawSpring, [0, 0.3], [0, 0.65], {
347
+ extrapolateLeft: "clamp",
348
+ extrapolateRight: "clamp",
349
+ });
350
+ const crossT = interpolate(f, [6.4 * fps, 7.2 * fps], [0, 1], {
351
+ extrapolateLeft: "clamp",
352
+ extrapolateRight: "clamp",
353
+ easing: Easing.out(Easing.quad),
354
+ });
355
+
356
+ // Badge pulse
357
+ const badgeSpring = gsapSpring(f, fps, 0.3, "bouncy");
358
+ const dotPulse = 0.6 + Math.sin(f * 0.15) * 0.4;
359
+
360
+ return (
361
+ <AbsoluteFill style={{ opacity: op }}>
362
+ <SafeZone>
363
+ {/* NEW RELEASE pill */}
364
+ <div
365
+ style={{
366
+ position: "absolute",
367
+ top: 20,
368
+ left: "50%",
369
+ transform: `translate(-50%, 0) scale(${interpolate(badgeSpring, [0, 1], [0.6, 1])})`,
370
+ opacity: interpolate(badgeSpring, [0, 0.3], [0, 1], {
371
+ extrapolateLeft: "clamp",
372
+ extrapolateRight: "clamp",
373
+ }),
374
+ display: "flex",
375
+ alignItems: "center",
376
+ gap: 12,
377
+ padding: "10px 22px",
378
+ background: "rgba(255,255,255,0.06)",
379
+ border: `1px solid ${C.borderLoud}`,
380
+ borderRadius: 9999,
381
+ backdropFilter: "blur(10px)",
382
+ }}
383
+ >
384
+ <div
385
+ style={{
386
+ width: 8,
387
+ height: 8,
388
+ borderRadius: "50%",
389
+ background: C.claude,
390
+ boxShadow: `0 0 ${10 + dotPulse * 10}px ${C.claude}`,
391
+ opacity: dotPulse,
392
+ }}
393
+ />
394
+ <span
395
+ style={{
396
+ fontFamily: MONO,
397
+ fontSize: 16,
398
+ fontWeight: 500,
399
+ color: C.fgSoft,
400
+ letterSpacing: "0.18em",
401
+ }}
402
+ >
403
+ QUIETLY LAUNCHED
404
+ </span>
405
+ </div>
406
+
407
+ {/* Anthropic A — large wordmark */}
408
+ <div
409
+ style={{
410
+ position: "absolute",
411
+ top: 90,
412
+ left: "50%",
413
+ transform: `translate(-50%, 0) scale(${logoScale})`,
414
+ opacity: logoOp,
415
+ display: "flex",
416
+ alignItems: "center",
417
+ gap: 16,
418
+ }}
419
+ >
420
+ <svg
421
+ width="60"
422
+ height="60"
423
+ viewBox="0 0 24 24"
424
+ style={{ color: C.claude, filter: "drop-shadow(0 0 24px rgba(212,102,58,0.35))" }}
425
+ >
426
+ <path
427
+ fill="currentColor"
428
+ d="M17.304 3.541h-3.672l6.696 16.918H24Zm-10.608 0L0 20.459h3.744l1.37-3.553h7.005l1.369 3.553h3.744L10.536 3.541Zm-.371 10.223L8.616 7.82l2.291 5.945Z"
429
+ />
430
+ </svg>
431
+ <span
432
+ style={{
433
+ fontFamily: FONT,
434
+ fontSize: 40,
435
+ fontWeight: 500,
436
+ color: C.fg,
437
+ letterSpacing: "-0.03em",
438
+ }}
439
+ >
440
+ Anthropic
441
+ </span>
442
+ </div>
443
+
444
+ {/* Subline */}
445
+ <div
446
+ style={{
447
+ position: "absolute",
448
+ top: 210,
449
+ width: "100%",
450
+ textAlign: "center",
451
+ padding: "0 56px",
452
+ }}
453
+ >
454
+ <KineticText
455
+ text="just dropped a feature that"
456
+ f={f}
457
+ fps={fps}
458
+ startSec={1.0}
459
+ wordStagger={0.05}
460
+ size={34}
461
+ weight={400}
462
+ color={C.fgMuted}
463
+ />
464
+ </div>
465
+
466
+ {/* Huge "CLAUDE DISPATCH" */}
467
+ <div
468
+ style={{
469
+ position: "absolute",
470
+ top: 340,
471
+ left: 0,
472
+ right: 0,
473
+ textAlign: "center",
474
+ transform: `scale(${smashScale}) translateX(${shakeX}px)`,
475
+ opacity: smashOp,
476
+ }}
477
+ >
478
+ <div
479
+ style={{
480
+ fontFamily: FONT,
481
+ fontSize: 150,
482
+ fontWeight: 700,
483
+ color: C.fg,
484
+ letterSpacing: "-0.055em",
485
+ lineHeight: 0.9,
486
+ }}
487
+ >
488
+ Claude
489
+ </div>
490
+ <div
491
+ style={{
492
+ fontFamily: FONT,
493
+ fontSize: 150,
494
+ fontWeight: 700,
495
+ color: C.claude,
496
+ letterSpacing: "-0.055em",
497
+ lineHeight: 0.9,
498
+ marginTop: 8,
499
+ textShadow: "0 0 60px rgba(212,102,58,0.4)",
500
+ }}
501
+ >
502
+ Dispatch
503
+ </div>
504
+ </div>
505
+
506
+ {/* Subtext + OpenClaw cameo */}
507
+ <div
508
+ style={{
509
+ position: "absolute",
510
+ bottom: 20,
511
+ left: "50%",
512
+ transform: "translate(-50%, 0)",
513
+ display: "flex",
514
+ alignItems: "center",
515
+ gap: 20,
516
+ }}
517
+ >
518
+ <div
519
+ style={{
520
+ fontFamily: FONT,
521
+ fontSize: 30,
522
+ fontWeight: 500,
523
+ color: C.fgSoft,
524
+ letterSpacing: "-0.02em",
525
+ opacity: interpolate(f, [4 * fps, 5 * fps], [0, 1], {
526
+ extrapolateLeft: "clamp",
527
+ extrapolateRight: "clamp",
528
+ }),
529
+ }}
530
+ >
531
+ Might end
532
+ </div>
533
+ <div
534
+ style={{
535
+ position: "relative",
536
+ display: "flex",
537
+ alignItems: "center",
538
+ gap: 10,
539
+ opacity: clawOp,
540
+ }}
541
+ >
542
+ <Img
543
+ src={staticFile("captures/your-asset.png" /* REFERENCE-STRIP */)}
544
+ style={{ width: 48, height: 48, opacity: 1 - crossT * 0.5 }}
545
+ />
546
+ <span
547
+ style={{
548
+ fontFamily: FONT,
549
+ fontSize: 30,
550
+ fontWeight: 600,
551
+ color: C.fgSoft,
552
+ letterSpacing: "-0.02em",
553
+ }}
554
+ >
555
+ OpenClaw
556
+ </span>
557
+ {/* Strike through line */}
558
+ <div
559
+ style={{
560
+ position: "absolute",
561
+ top: "50%",
562
+ left: 0,
563
+ width: `${crossT * 100}%`,
564
+ height: 4,
565
+ background: C.danger,
566
+ transformOrigin: "0 50%",
567
+ borderRadius: 2,
568
+ boxShadow: `0 0 12px ${C.danger}`,
569
+ }}
570
+ />
571
+ </div>
572
+ <div
573
+ style={{
574
+ fontFamily: FONT,
575
+ fontSize: 30,
576
+ fontWeight: 500,
577
+ color: C.fgSoft,
578
+ letterSpacing: "-0.02em",
579
+ opacity: interpolate(f, [6.2 * fps, 7 * fps], [0, 1], {
580
+ extrapolateLeft: "clamp",
581
+ extrapolateRight: "clamp",
582
+ }),
583
+ }}
584
+ >
585
+ overnight.
586
+ </div>
587
+ </div>
588
+ </SafeZone>
589
+ </AbsoluteFill>
590
+ );
591
+ };
592
+
593
+ // ═══════════════════════════════════════════════════════════════
594
+ // SCENE 2 — THE IDEA (7.8-20s)
595
+ // ═══════════════════════════════════════════════════════════════
596
+
597
+ const PhoneFrame: React.FC<{
598
+ children: React.ReactNode;
599
+ scale?: number;
600
+ style?: React.CSSProperties;
601
+ }> = ({ children, scale = 1, style }) => (
602
+ <div
603
+ style={{
604
+ width: 340 * scale,
605
+ height: 700 * scale,
606
+ borderRadius: 48 * scale,
607
+ background: "#1a1a1d",
608
+ border: `${2 * scale}px solid #2a2a2d`,
609
+ padding: 10 * scale,
610
+ boxShadow: [
611
+ "0 40px 80px -20px rgba(0,0,0,0.7)",
612
+ "0 20px 40px -15px rgba(212,102,58,0.15)",
613
+ "inset 0 1px 0 rgba(255,255,255,0.08)",
614
+ ].join(", "),
615
+ position: "relative",
616
+ overflow: "hidden",
617
+ ...style,
618
+ }}
619
+ >
620
+ {/* Notch */}
621
+ <div
622
+ style={{
623
+ position: "absolute",
624
+ top: 22 * scale,
625
+ left: "50%",
626
+ transform: "translate(-50%, 0)",
627
+ width: 100 * scale,
628
+ height: 30 * scale,
629
+ background: "#000",
630
+ borderRadius: 18 * scale,
631
+ zIndex: 5,
632
+ }}
633
+ />
634
+ <div
635
+ style={{
636
+ width: "100%",
637
+ height: "100%",
638
+ borderRadius: 38 * scale,
639
+ background: "#0c0c0e",
640
+ overflow: "hidden",
641
+ position: "relative",
642
+ }}
643
+ >
644
+ {children}
645
+ </div>
646
+ </div>
647
+ );
648
+
649
+ const IdeaScene: React.FC<SP> = ({ frame, fps }) => {
650
+ const op = so(frame, fps, SEGS[1].s, SEGS[1].e);
651
+ if (op === 0) return null;
652
+ const f = lf(frame, fps, SEGS[1].s);
653
+
654
+ // Phone slides in from left
655
+ const phoneSpring = gsapSpring(f, fps, 0.2, "glass");
656
+ const phoneX = interpolate(phoneSpring, [0, 1], [-400, 0]);
657
+
658
+ // Computer slides in from right
659
+ const compSpring = gsapSpring(f, fps, 0.5, "glass");
660
+ const compX = interpolate(compSpring, [0, 1], [400, 0]);
661
+
662
+ // Text typing on phone (starts at 2s local)
663
+ const typedChars = Math.floor(
664
+ interpolate(f, [2 * fps, 4 * fps], [0, 28], {
665
+ extrapolateLeft: "clamp",
666
+ extrapolateRight: "clamp",
667
+ }),
668
+ );
669
+ const phoneText = "fix the bugs in my code".slice(0, typedChars);
670
+ const cursorVisible = Math.floor(f / 10) % 2 === 0;
671
+
672
+ // Connecting line draws from phone → computer at 3.5s local
673
+ const lineT = interpolate(f, [3.5 * fps, 5 * fps], [0, 1], {
674
+ extrapolateLeft: "clamp",
675
+ extrapolateRight: "clamp",
676
+ easing: Easing.inOut(Easing.quad),
677
+ });
678
+
679
+ // Tasks appearing on computer side
680
+ const tasks = [
681
+ { label: "Parsing codebase...", color: C.fgMuted },
682
+ { label: "Locating bug in auth.ts", color: C.fgSoft },
683
+ { label: "Writing fix + tests", color: C.claude },
684
+ ];
685
+
686
+ return (
687
+ <AbsoluteFill style={{ opacity: op }}>
688
+ <SafeZone>
689
+ {/* Section label */}
690
+ <div
691
+ style={{
692
+ position: "absolute",
693
+ top: 10,
694
+ left: "50%",
695
+ transform: "translate(-50%, 0)",
696
+ fontFamily: MONO,
697
+ fontSize: 18,
698
+ fontWeight: 500,
699
+ color: C.fgDim,
700
+ letterSpacing: "0.24em",
701
+ opacity: interpolate(f, [0.3 * fps, 0.8 * fps], [0, 1], {
702
+ extrapolateLeft: "clamp",
703
+ extrapolateRight: "clamp",
704
+ }),
705
+ }}
706
+ >
707
+ HERE'S THE IDEA
708
+ </div>
709
+
710
+ {/* Headline */}
711
+ <div
712
+ style={{
713
+ position: "absolute",
714
+ top: 60,
715
+ width: "100%",
716
+ textAlign: "center",
717
+ padding: "0 48px",
718
+ }}
719
+ >
720
+ <KineticText
721
+ text="Text it. Walk away."
722
+ f={f}
723
+ fps={fps}
724
+ startSec={0.6}
725
+ wordStagger={0.08}
726
+ size={72}
727
+ weight={700}
728
+ color={C.fg}
729
+ />
730
+ </div>
731
+
732
+ {/* Phone on left */}
733
+ <div
734
+ style={{
735
+ position: "absolute",
736
+ top: 240,
737
+ left: 40,
738
+ transform: `translateX(${phoneX}px)`,
739
+ }}
740
+ >
741
+ <PhoneFrame scale={0.75}>
742
+ {/* iMessage-style header */}
743
+ <div
744
+ style={{
745
+ padding: "60px 18px 14px",
746
+ borderBottom: "1px solid rgba(255,255,255,0.06)",
747
+ display: "flex",
748
+ alignItems: "center",
749
+ gap: 10,
750
+ }}
751
+ >
752
+ <div
753
+ style={{
754
+ width: 32,
755
+ height: 32,
756
+ borderRadius: "50%",
757
+ background: `linear-gradient(135deg, ${C.claude}, ${C.claudeSoft})`,
758
+ display: "flex",
759
+ alignItems: "center",
760
+ justifyContent: "center",
761
+ fontFamily: FONT,
762
+ fontSize: 16,
763
+ fontWeight: 700,
764
+ color: "#fff",
765
+ }}
766
+ >
767
+ C
768
+ </div>
769
+ <div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
770
+ <div style={{ fontFamily: FONT, fontSize: 14, fontWeight: 600, color: C.fg }}>
771
+ Claude Dispatch
772
+ </div>
773
+ <div
774
+ style={{
775
+ fontFamily: MONO,
776
+ fontSize: 9,
777
+ color: C.safe,
778
+ letterSpacing: "0.1em",
779
+ }}
780
+ >
781
+ ● ACTIVE
782
+ </div>
783
+ </div>
784
+ </div>
785
+ {/* Bubble stack */}
786
+ <div style={{ padding: "18px 16px", display: "flex", flexDirection: "column", gap: 10 }}>
787
+ {/* User message bubble — with typing */}
788
+ <div style={{ alignSelf: "flex-end", maxWidth: "85%" }}>
789
+ <div
790
+ style={{
791
+ background: C.claude,
792
+ color: "#fff",
793
+ padding: "10px 14px",
794
+ borderRadius: "18px 18px 4px 18px",
795
+ fontFamily: FONT,
796
+ fontSize: 14,
797
+ fontWeight: 500,
798
+ boxShadow: "0 4px 12px rgba(212,102,58,0.3)",
799
+ minHeight: 20,
800
+ }}
801
+ >
802
+ {phoneText}
803
+ {typedChars > 0 && typedChars < 28 && cursorVisible && "|"}
804
+ </div>
805
+ </div>
806
+
807
+ {/* Reply bubble — appears after typing done */}
808
+ <div
809
+ style={{
810
+ alignSelf: "flex-start",
811
+ maxWidth: "85%",
812
+ opacity: interpolate(f, [4.2 * fps, 5 * fps], [0, 1], {
813
+ extrapolateLeft: "clamp",
814
+ extrapolateRight: "clamp",
815
+ }),
816
+ transform: `translateY(${interpolate(f, [4.2 * fps, 5 * fps], [10, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" })}px)`,
817
+ }}
818
+ >
819
+ <div
820
+ style={{
821
+ background: "rgba(255,255,255,0.08)",
822
+ color: C.fgSoft,
823
+ padding: "10px 14px",
824
+ borderRadius: "18px 18px 18px 4px",
825
+ fontFamily: FONT,
826
+ fontSize: 13,
827
+ fontWeight: 400,
828
+ }}
829
+ >
830
+ On it. Running now.
831
+ </div>
832
+ </div>
833
+ </div>
834
+ <Scanline frame={frame} />
835
+ </PhoneFrame>
836
+ </div>
837
+
838
+ {/* Computer / terminal on right */}
839
+ <div
840
+ style={{
841
+ position: "absolute",
842
+ top: 280,
843
+ right: 40,
844
+ width: 560,
845
+ transform: `translateX(${compX}px)`,
846
+ }}
847
+ >
848
+ <div
849
+ style={{
850
+ background: C.darkGreen,
851
+ border: "1px solid rgba(255,255,255,0.08)",
852
+ borderRadius: 18,
853
+ overflow: "hidden",
854
+ boxShadow: [
855
+ "0 40px 80px -20px rgba(0,0,0,0.7)",
856
+ "0 0 40px rgba(79,196,106,0.08)",
857
+ "inset 0 1px 0 rgba(255,255,255,0.06)",
858
+ ].join(", "),
859
+ }}
860
+ >
861
+ {/* Title bar */}
862
+ <div
863
+ style={{
864
+ padding: "14px 18px",
865
+ borderBottom: "1px solid rgba(255,255,255,0.08)",
866
+ display: "flex",
867
+ alignItems: "center",
868
+ gap: 8,
869
+ }}
870
+ >
871
+ <div style={{ width: 10, height: 10, borderRadius: "50%", background: "#ff5f57" }} />
872
+ <div style={{ width: 10, height: 10, borderRadius: "50%", background: "#febc2e" }} />
873
+ <div style={{ width: 10, height: 10, borderRadius: "50%", background: "#28c840" }} />
874
+ <div
875
+ style={{
876
+ marginLeft: 12,
877
+ fontFamily: MONO,
878
+ fontSize: 13,
879
+ color: "rgba(255,255,255,0.4)",
880
+ }}
881
+ >
882
+ dispatch --run
883
+ </div>
884
+ </div>
885
+ {/* Body */}
886
+ <div style={{ padding: "20px 22px", minHeight: 360 }}>
887
+ {tasks.map((t, i) => {
888
+ const delay = 1.5 + i * 0.55;
889
+ const s = gsapSpring(f, fps, delay, "snappy");
890
+ const y = interpolate(s, [0, 1], [20, 0]);
891
+ const o = interpolate(s, [0, 0.4], [0, 1], {
892
+ extrapolateLeft: "clamp",
893
+ extrapolateRight: "clamp",
894
+ });
895
+ return (
896
+ <div
897
+ key={t.label}
898
+ style={{
899
+ display: "flex",
900
+ alignItems: "center",
901
+ gap: 12,
902
+ padding: "10px 0",
903
+ fontFamily: MONO,
904
+ fontSize: 17,
905
+ opacity: o,
906
+ transform: `translateY(${y}px)`,
907
+ }}
908
+ >
909
+ <span style={{ color: C.safe, fontWeight: 600 }}>
910
+ {i === 2 && f > (3 + i * 0.55) * fps ? "●" : "○"}
911
+ </span>
912
+ <span style={{ color: t.color }}>{t.label}</span>
913
+ </div>
914
+ );
915
+ })}
916
+ {/* Output preview */}
917
+ <div
918
+ style={{
919
+ marginTop: 12,
920
+ padding: "12px 14px",
921
+ background: "rgba(0,0,0,0.4)",
922
+ border: "1px solid rgba(79,196,106,0.18)",
923
+ borderRadius: 8,
924
+ fontFamily: MONO,
925
+ fontSize: 13,
926
+ color: C.safe,
927
+ opacity: interpolate(f, [4 * fps, 5.2 * fps], [0, 1], {
928
+ extrapolateLeft: "clamp",
929
+ extrapolateRight: "clamp",
930
+ }),
931
+ }}
932
+ >
933
+ {"> "}
934
+ {`auth.ts — bug patched. 3 tests passing.`}
935
+ </div>
936
+ </div>
937
+ <Scanline frame={frame} color="rgba(79,196,106,0.08)" />
938
+ </div>
939
+
940
+ {/* Subtitle below computer */}
941
+ <div
942
+ style={{
943
+ marginTop: 20,
944
+ fontFamily: FONT,
945
+ fontSize: 22,
946
+ fontWeight: 500,
947
+ color: C.fgMuted,
948
+ letterSpacing: "-0.015em",
949
+ lineHeight: 1.4,
950
+ opacity: interpolate(f, [6 * fps, 7 * fps], [0, 1], {
951
+ extrapolateLeft: "clamp",
952
+ extrapolateRight: "clamp",
953
+ }),
954
+ }}
955
+ >
956
+ Full tasks. In the background.
957
+ </div>
958
+ </div>
959
+
960
+ {/* Connecting dotted line — phone → computer */}
961
+ <svg
962
+ style={{
963
+ position: "absolute",
964
+ top: 580,
965
+ left: 310,
966
+ width: 260,
967
+ height: 60,
968
+ pointerEvents: "none",
969
+ }}
970
+ viewBox="0 0 260 60"
971
+ >
972
+ <defs>
973
+ <linearGradient id="flowGrad" x1="0%" y1="0%" x2="100%" y2="0%">
974
+ <stop offset="0%" stopColor={C.claude} />
975
+ <stop offset="100%" stopColor={C.safe} />
976
+ </linearGradient>
977
+ </defs>
978
+ <path
979
+ d="M 10 30 Q 130 -20 250 30"
980
+ fill="none"
981
+ stroke="url(#flowGrad)"
982
+ strokeWidth="3"
983
+ strokeDasharray="4 8"
984
+ strokeDashoffset={-frame * 0.8}
985
+ strokeLinecap="round"
986
+ opacity={lineT}
987
+ />
988
+ {/* Arrowhead */}
989
+ <circle cx="250" cy="30" r="6" fill={C.safe} opacity={lineT} />
990
+ </svg>
991
+ </SafeZone>
992
+ </AbsoluteFill>
993
+ );
994
+ };
995
+
996
+ // ═══════════════════════════════════════════════════════════════
997
+ // SCENE 3 — EXAMPLE (19.5-33s) — Dispatch clip + task stack
998
+ // ═══════════════════════════════════════════════════════════════
999
+
1000
+ const ExampleScene: React.FC<SP> = ({ frame, fps }) => {
1001
+ const op = so(frame, fps, SEGS[2].s, SEGS[2].e);
1002
+ if (op === 0) return null;
1003
+ const f = lf(frame, fps, SEGS[2].s);
1004
+
1005
+ // Video clip slides in
1006
+ const videoSpring = gsapSpring(f, fps, 0.2, "glass");
1007
+ const videoY = interpolate(videoSpring, [0, 1], [60, 0]);
1008
+ const videoOp = interpolate(videoSpring, [0, 0.5], [0, 1], {
1009
+ extrapolateLeft: "clamp",
1010
+ extrapolateRight: "clamp",
1011
+ });
1012
+
1013
+ const tasks = [
1014
+ { t: "☕", label: "Grabbing coffee", status: "idle", delay: 1.2 },
1015
+ { t: ">", label: "Fix bugs in my code", status: "run", delay: 2.4 },
1016
+ { t: "⚡", label: "Pull data from 50 sites", status: "run", delay: 3.8 },
1017
+ { t: "✓", label: "Pinged you — all done", status: "done", delay: 7.5 },
1018
+ ];
1019
+
1020
+ // Counter animates from 0 → 50 from 5s to 8s
1021
+ const count = Math.floor(
1022
+ interpolate(f, [5 * fps, 8 * fps], [0, 50], {
1023
+ extrapolateLeft: "clamp",
1024
+ extrapolateRight: "clamp",
1025
+ easing: Easing.out(Easing.quad),
1026
+ }),
1027
+ );
1028
+
1029
+ return (
1030
+ <AbsoluteFill style={{ opacity: op }}>
1031
+ <SafeZone>
1032
+ {/* Section label */}
1033
+ <div
1034
+ style={{
1035
+ position: "absolute",
1036
+ top: 10,
1037
+ left: "50%",
1038
+ transform: "translate(-50%, 0)",
1039
+ fontFamily: MONO,
1040
+ fontSize: 18,
1041
+ color: C.fgDim,
1042
+ letterSpacing: "0.24em",
1043
+ }}
1044
+ >
1045
+ WHILE YOU'RE AWAY
1046
+ </div>
1047
+
1048
+ {/* Video clip — trimmed section of claude-dispatch.mp4 */}
1049
+ <div
1050
+ style={{
1051
+ position: "absolute",
1052
+ top: 60,
1053
+ left: "50%",
1054
+ transform: `translate(-50%, ${videoY}px)`,
1055
+ opacity: videoOp,
1056
+ width: 900,
1057
+ height: 500,
1058
+ borderRadius: 22,
1059
+ overflow: "hidden",
1060
+ border: `1px solid ${C.borderLoud}`,
1061
+ boxShadow: [
1062
+ "0 40px 90px -20px rgba(0,0,0,0.7)",
1063
+ "0 0 60px rgba(212,102,58,0.15)",
1064
+ "inset 0 1px 0 rgba(255,255,255,0.08)",
1065
+ ].join(", "),
1066
+ background: "#000",
1067
+ }}
1068
+ >
1069
+ <Sequence from={Math.round(SEGS[2].s * fps)} durationInFrames={Math.round(8 * fps)}>
1070
+ {/* REFERENCE-STRIP: <OffthreadVideo> removed — bring your own clip */}
1071
+ </Sequence>
1072
+ {/* Corner label */}
1073
+ <div
1074
+ style={{
1075
+ position: "absolute",
1076
+ top: 16,
1077
+ left: 16,
1078
+ padding: "6px 14px",
1079
+ background: "rgba(0,0,0,0.55)",
1080
+ backdropFilter: "blur(8px)",
1081
+ borderRadius: 9999,
1082
+ border: "1px solid rgba(255,255,255,0.12)",
1083
+ fontFamily: MONO,
1084
+ fontSize: 13,
1085
+ fontWeight: 500,
1086
+ color: "#fff",
1087
+ letterSpacing: "0.12em",
1088
+ display: "flex",
1089
+ alignItems: "center",
1090
+ gap: 8,
1091
+ }}
1092
+ >
1093
+ <div
1094
+ style={{
1095
+ width: 6,
1096
+ height: 6,
1097
+ borderRadius: "50%",
1098
+ background: C.danger,
1099
+ boxShadow: `0 0 8px ${C.danger}`,
1100
+ opacity: 0.5 + Math.sin(f * 0.2) * 0.5,
1101
+ }}
1102
+ />
1103
+ LIVE · DISPATCH
1104
+ </div>
1105
+ </div>
1106
+
1107
+ {/* Task stack — right side overlay */}
1108
+ <div
1109
+ style={{
1110
+ position: "absolute",
1111
+ top: 610,
1112
+ left: "50%",
1113
+ transform: "translate(-50%, 0)",
1114
+ width: 900,
1115
+ display: "flex",
1116
+ flexDirection: "column",
1117
+ gap: 14,
1118
+ }}
1119
+ >
1120
+ {tasks.map((task) => {
1121
+ const s = gsapSpring(f, fps, task.delay, "snappy");
1122
+ const x = interpolate(s, [0, 1], [50, 0]);
1123
+ const o = interpolate(s, [0, 0.4], [0, 1], {
1124
+ extrapolateLeft: "clamp",
1125
+ extrapolateRight: "clamp",
1126
+ });
1127
+ const isDone = task.status === "done";
1128
+ const isRun = task.status === "run";
1129
+ return (
1130
+ <div
1131
+ key={task.label}
1132
+ style={{
1133
+ padding: "18px 26px",
1134
+ background: isDone
1135
+ ? "rgba(79,196,106,0.08)"
1136
+ : "rgba(255,255,255,0.04)",
1137
+ border: `1px solid ${isDone ? "rgba(79,196,106,0.3)" : C.border}`,
1138
+ borderRadius: 16,
1139
+ display: "flex",
1140
+ alignItems: "center",
1141
+ gap: 18,
1142
+ opacity: o,
1143
+ transform: `translateX(${x}px)`,
1144
+ backdropFilter: "blur(10px)",
1145
+ }}
1146
+ >
1147
+ <div
1148
+ style={{
1149
+ width: 44,
1150
+ height: 44,
1151
+ borderRadius: "50%",
1152
+ background: isDone
1153
+ ? C.safe
1154
+ : isRun
1155
+ ? C.claude
1156
+ : "rgba(255,255,255,0.08)",
1157
+ display: "flex",
1158
+ alignItems: "center",
1159
+ justifyContent: "center",
1160
+ fontFamily: MONO,
1161
+ fontSize: 22,
1162
+ fontWeight: 700,
1163
+ color: isDone || isRun ? "#0a0a0b" : C.fgSoft,
1164
+ flexShrink: 0,
1165
+ }}
1166
+ >
1167
+ {task.t}
1168
+ </div>
1169
+ <div
1170
+ style={{
1171
+ flex: 1,
1172
+ fontFamily: FONT,
1173
+ fontSize: 28,
1174
+ fontWeight: 500,
1175
+ color: C.fg,
1176
+ letterSpacing: "-0.015em",
1177
+ }}
1178
+ >
1179
+ {task.label}
1180
+ </div>
1181
+ {task.label.includes("50") && (
1182
+ <div
1183
+ style={{
1184
+ fontFamily: MONO,
1185
+ fontSize: 32,
1186
+ fontWeight: 700,
1187
+ color: C.claude,
1188
+ minWidth: 72,
1189
+ textAlign: "right",
1190
+ }}
1191
+ >
1192
+ {count}/50
1193
+ </div>
1194
+ )}
1195
+ {isDone && (
1196
+ <div
1197
+ style={{
1198
+ fontFamily: MONO,
1199
+ fontSize: 14,
1200
+ fontWeight: 600,
1201
+ color: C.safe,
1202
+ letterSpacing: "0.12em",
1203
+ }}
1204
+ >
1205
+ DONE
1206
+ </div>
1207
+ )}
1208
+ </div>
1209
+ );
1210
+ })}
1211
+ </div>
1212
+ </SafeZone>
1213
+ </AbsoluteFill>
1214
+ );
1215
+ };
1216
+
1217
+ // ═══════════════════════════════════════════════════════════════
1218
+ // SCENE 4 — WHY IT MATTERS / OPENCLAW INTRO (32.5-41s)
1219
+ // ═══════════════════════════════════════════════════════════════
1220
+
1221
+ const WhyScene: React.FC<SP> = ({ frame, fps }) => {
1222
+ const op = so(frame, fps, SEGS[3].s, SEGS[3].e);
1223
+ if (op === 0) return null;
1224
+ const f = lf(frame, fps, SEGS[3].s);
1225
+
1226
+ const clawSpring = gsapSpring(f, fps, 1.0, "gentle");
1227
+ const clawScale = interpolate(clawSpring, [0, 1], [0.5, 1]);
1228
+ const clawOp = interpolate(clawSpring, [0, 0.4], [0, 1], {
1229
+ extrapolateLeft: "clamp",
1230
+ extrapolateRight: "clamp",
1231
+ });
1232
+ // Slight sway
1233
+ const sway = Math.sin(f * 0.05) * 5;
1234
+ // Danger ring pulse
1235
+ const ringScale = 1 + (Math.sin(f * 0.12) + 1) * 0.08;
1236
+ const ringOp = 0.35 + Math.sin(f * 0.12) * 0.25;
1237
+
1238
+ return (
1239
+ <AbsoluteFill style={{ opacity: op }}>
1240
+ <SafeZone>
1241
+ <div
1242
+ style={{
1243
+ position: "absolute",
1244
+ top: 30,
1245
+ left: "50%",
1246
+ transform: "translate(-50%, 0)",
1247
+ fontFamily: MONO,
1248
+ fontSize: 18,
1249
+ color: C.fgDim,
1250
+ letterSpacing: "0.24em",
1251
+ opacity: interpolate(f, [0.2 * fps, 0.8 * fps], [0, 1], {
1252
+ extrapolateLeft: "clamp",
1253
+ extrapolateRight: "clamp",
1254
+ }),
1255
+ }}
1256
+ >
1257
+ WHY THIS MATTERS
1258
+ </div>
1259
+
1260
+ <div
1261
+ style={{
1262
+ position: "absolute",
1263
+ top: 90,
1264
+ width: "100%",
1265
+ textAlign: "center",
1266
+ padding: "0 48px",
1267
+ }}
1268
+ >
1269
+ <KineticText
1270
+ text="People tried this before."
1271
+ f={f}
1272
+ fps={fps}
1273
+ startSec={0.5}
1274
+ wordStagger={0.07}
1275
+ size={60}
1276
+ weight={700}
1277
+ color={C.fg}
1278
+ />
1279
+ </div>
1280
+
1281
+ {/* OpenClaw mascot — large, with danger ring */}
1282
+ <div
1283
+ style={{
1284
+ position: "absolute",
1285
+ top: 300,
1286
+ left: "50%",
1287
+ transform: `translate(-50%, 0) scale(${clawScale})`,
1288
+ opacity: clawOp,
1289
+ width: 360,
1290
+ height: 360,
1291
+ }}
1292
+ >
1293
+ {/* Danger ring */}
1294
+ <div
1295
+ style={{
1296
+ position: "absolute",
1297
+ inset: -20,
1298
+ borderRadius: "50%",
1299
+ border: `2px solid ${C.danger}`,
1300
+ opacity: ringOp,
1301
+ transform: `scale(${ringScale})`,
1302
+ boxShadow: `0 0 40px ${C.danger}`,
1303
+ }}
1304
+ />
1305
+ {/* Second ring offset */}
1306
+ <div
1307
+ style={{
1308
+ position: "absolute",
1309
+ inset: -40,
1310
+ borderRadius: "50%",
1311
+ border: `1px solid ${C.danger}`,
1312
+ opacity: ringOp * 0.5,
1313
+ transform: `scale(${ringScale * 0.95})`,
1314
+ }}
1315
+ />
1316
+ <Img
1317
+ src={staticFile("captures/your-asset.png" /* REFERENCE-STRIP */)}
1318
+ style={{
1319
+ width: "100%",
1320
+ height: "100%",
1321
+ transform: `translateX(${sway}px) rotate(${sway * 0.3}deg)`,
1322
+ filter: "drop-shadow(0 20px 40px rgba(226,88,34,0.4))",
1323
+ }}
1324
+ />
1325
+ </div>
1326
+
1327
+ {/* Tag */}
1328
+ <div
1329
+ style={{
1330
+ position: "absolute",
1331
+ top: 690,
1332
+ left: "50%",
1333
+ transform: "translate(-50%, 0)",
1334
+ fontFamily: FONT,
1335
+ fontSize: 32,
1336
+ fontWeight: 600,
1337
+ color: C.danger,
1338
+ letterSpacing: "0.14em",
1339
+ textTransform: "uppercase",
1340
+ opacity: interpolate(f, [2.5 * fps, 3.2 * fps], [0, 1], {
1341
+ extrapolateLeft: "clamp",
1342
+ extrapolateRight: "clamp",
1343
+ }),
1344
+ }}
1345
+ >
1346
+ openclaw
1347
+ </div>
1348
+
1349
+ {/* Subline */}
1350
+ <div
1351
+ style={{
1352
+ position: "absolute",
1353
+ bottom: 30,
1354
+ width: "100%",
1355
+ textAlign: "center",
1356
+ padding: "0 60px",
1357
+ opacity: interpolate(f, [4 * fps, 5 * fps], [0, 1], {
1358
+ extrapolateLeft: "clamp",
1359
+ extrapolateRight: "clamp",
1360
+ }),
1361
+ }}
1362
+ >
1363
+ <div
1364
+ style={{
1365
+ fontFamily: FONT,
1366
+ fontSize: 34,
1367
+ fontWeight: 500,
1368
+ color: C.fgSoft,
1369
+ letterSpacing: "-0.02em",
1370
+ lineHeight: 1.35,
1371
+ }}
1372
+ >
1373
+ It had{" "}
1374
+ <span style={{ color: C.danger, fontWeight: 700 }}>
1375
+ serious issues.
1376
+ </span>
1377
+ </div>
1378
+ </div>
1379
+ </SafeZone>
1380
+ </AbsoluteFill>
1381
+ );
1382
+ };
1383
+
1384
+ // ═══════════════════════════════════════════════════════════════
1385
+ // SCENE 5 — OPENCLAW ISSUES (40.5-52s)
1386
+ // ═══════════════════════════════════════════════════════════════
1387
+
1388
+ const IssueCard: React.FC<{
1389
+ idx: number;
1390
+ icon: React.ReactNode;
1391
+ title: string;
1392
+ detail: string;
1393
+ f: number;
1394
+ fps: number;
1395
+ baseDelay: number;
1396
+ }> = ({ idx, icon, title, detail, f, fps, baseDelay }) => {
1397
+ const delay = baseDelay + idx * 0.55;
1398
+ const s = gsapSpring(f, fps, delay, "snappy");
1399
+ const x = interpolate(s, [0, 1], [120, 0]);
1400
+ const o = interpolate(s, [0, 0.4], [0, 1], {
1401
+ extrapolateLeft: "clamp",
1402
+ extrapolateRight: "clamp",
1403
+ });
1404
+ // Inner "alarm" pulse on icon
1405
+ const pulse = 0.7 + Math.sin(f * 0.15 + idx) * 0.3;
1406
+
1407
+ return (
1408
+ <div
1409
+ style={{
1410
+ padding: "22px 28px",
1411
+ background: `linear-gradient(135deg, rgba(226,88,34,0.08), rgba(226,88,34,0.02))`,
1412
+ border: `1px solid ${C.danger}`,
1413
+ borderLeftWidth: 4,
1414
+ borderRadius: 16,
1415
+ display: "flex",
1416
+ alignItems: "center",
1417
+ gap: 20,
1418
+ opacity: o,
1419
+ transform: `translateX(${x}px)`,
1420
+ position: "relative",
1421
+ overflow: "hidden",
1422
+ }}
1423
+ >
1424
+ {/* Subtle diagonal stripe */}
1425
+ <div
1426
+ style={{
1427
+ position: "absolute",
1428
+ inset: 0,
1429
+ background: `repeating-linear-gradient(45deg, transparent, transparent 20px, rgba(226,88,34,0.03) 20px, rgba(226,88,34,0.03) 40px)`,
1430
+ pointerEvents: "none",
1431
+ }}
1432
+ />
1433
+ <div
1434
+ style={{
1435
+ width: 64,
1436
+ height: 64,
1437
+ borderRadius: 14,
1438
+ background: `rgba(226,88,34,${0.15 + pulse * 0.1})`,
1439
+ border: `1px solid rgba(226,88,34,${0.3 + pulse * 0.3})`,
1440
+ display: "flex",
1441
+ alignItems: "center",
1442
+ justifyContent: "center",
1443
+ flexShrink: 0,
1444
+ boxShadow: `0 0 ${20 + pulse * 15}px rgba(226,88,34,${0.2 + pulse * 0.2})`,
1445
+ }}
1446
+ >
1447
+ {icon}
1448
+ </div>
1449
+ <div style={{ flex: 1, zIndex: 1 }}>
1450
+ <div
1451
+ style={{
1452
+ fontFamily: FONT,
1453
+ fontSize: 32,
1454
+ fontWeight: 700,
1455
+ color: C.fg,
1456
+ letterSpacing: "-0.025em",
1457
+ lineHeight: 1.1,
1458
+ }}
1459
+ >
1460
+ {title}
1461
+ </div>
1462
+ <div
1463
+ style={{
1464
+ fontFamily: FONT,
1465
+ fontSize: 18,
1466
+ fontWeight: 400,
1467
+ color: C.fgMuted,
1468
+ marginTop: 4,
1469
+ letterSpacing: "-0.01em",
1470
+ }}
1471
+ >
1472
+ {detail}
1473
+ </div>
1474
+ </div>
1475
+ <div
1476
+ style={{
1477
+ padding: "6px 14px",
1478
+ background: C.danger,
1479
+ borderRadius: 9999,
1480
+ fontFamily: MONO,
1481
+ fontSize: 12,
1482
+ fontWeight: 700,
1483
+ color: "#fff",
1484
+ letterSpacing: "0.14em",
1485
+ flexShrink: 0,
1486
+ }}
1487
+ >
1488
+ ISSUE
1489
+ </div>
1490
+ </div>
1491
+ );
1492
+ };
1493
+
1494
+ const IssuesScene: React.FC<SP> = ({ frame, fps }) => {
1495
+ const op = so(frame, fps, SEGS[4].s, SEGS[4].e);
1496
+ if (op === 0) return null;
1497
+ const f = lf(frame, fps, SEGS[4].s);
1498
+
1499
+ return (
1500
+ <AbsoluteFill style={{ opacity: op }}>
1501
+ <SafeZone>
1502
+ {/* Label */}
1503
+ <div
1504
+ style={{
1505
+ position: "absolute",
1506
+ top: 20,
1507
+ left: "50%",
1508
+ transform: "translate(-50%, 0)",
1509
+ padding: "8px 20px",
1510
+ background: "rgba(226,88,34,0.12)",
1511
+ border: `1px solid rgba(226,88,34,0.3)`,
1512
+ borderRadius: 9999,
1513
+ fontFamily: MONO,
1514
+ fontSize: 16,
1515
+ fontWeight: 500,
1516
+ color: C.dangerSoft,
1517
+ letterSpacing: "0.2em",
1518
+ opacity: interpolate(f, [0.2 * fps, 0.7 * fps], [0, 1], {
1519
+ extrapolateLeft: "clamp",
1520
+ extrapolateRight: "clamp",
1521
+ }),
1522
+ }}
1523
+ >
1524
+ OPENCLAW · WHAT WENT WRONG
1525
+ </div>
1526
+
1527
+ {/* Title */}
1528
+ <div
1529
+ style={{
1530
+ position: "absolute",
1531
+ top: 90,
1532
+ width: "100%",
1533
+ textAlign: "center",
1534
+ padding: "0 48px",
1535
+ }}
1536
+ >
1537
+ <KineticText
1538
+ text="Three deal-breakers."
1539
+ f={f}
1540
+ fps={fps}
1541
+ startSec={0.5}
1542
+ wordStagger={0.08}
1543
+ size={58}
1544
+ weight={700}
1545
+ color={C.fg}
1546
+ />
1547
+ </div>
1548
+
1549
+ {/* Issue cards */}
1550
+ <div
1551
+ style={{
1552
+ position: "absolute",
1553
+ top: 260,
1554
+ left: 40,
1555
+ right: 40,
1556
+ display: "flex",
1557
+ flexDirection: "column",
1558
+ gap: 20,
1559
+ }}
1560
+ >
1561
+ <IssueCard
1562
+ idx={0}
1563
+ baseDelay={1.1}
1564
+ f={f}
1565
+ fps={fps}
1566
+ icon={
1567
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none">
1568
+ <path
1569
+ d="M15 7a4 4 0 1 1-3.5 6.37M10.5 13.37l-6 6V22h3l.5-2.5 2.5-.5v-2.5h2.5v-2.5"
1570
+ stroke={C.dangerSoft}
1571
+ strokeWidth="2"
1572
+ strokeLinecap="round"
1573
+ strokeLinejoin="round"
1574
+ />
1575
+ </svg>
1576
+ }
1577
+ title="API keys leaking"
1578
+ detail="Tokens exposed in every request trace."
1579
+ />
1580
+ <IssueCard
1581
+ idx={1}
1582
+ baseDelay={1.1}
1583
+ f={f}
1584
+ fps={fps}
1585
+ icon={
1586
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none">
1587
+ <path
1588
+ d="M12 3 4 7v5c0 4.5 3 8 8 9 5-1 8-4.5 8-9V7l-8-4Z"
1589
+ stroke={C.dangerSoft}
1590
+ strokeWidth="2"
1591
+ strokeLinejoin="round"
1592
+ />
1593
+ <path d="m9 11 3 3m0-3-3 3" stroke={C.dangerSoft} strokeWidth="2" strokeLinecap="round" />
1594
+ </svg>
1595
+ }
1596
+ title="Security? Shaky."
1597
+ detail="No sandbox. Any prompt was a live wire."
1598
+ />
1599
+ <IssueCard
1600
+ idx={2}
1601
+ baseDelay={1.1}
1602
+ f={f}
1603
+ fps={fps}
1604
+ icon={
1605
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none">
1606
+ <path
1607
+ d="M12 2v4m0 12v4m10-10h-4M6 12H2m15.07-7.07-2.83 2.83M9.76 14.24l-2.83 2.83m0-12.14 2.83 2.83m4.48 4.48 2.83 2.83"
1608
+ stroke={C.dangerSoft}
1609
+ strokeWidth="2"
1610
+ strokeLinecap="round"
1611
+ />
1612
+ <circle cx="12" cy="12" r="3" stroke={C.dangerSoft} strokeWidth="2" />
1613
+ </svg>
1614
+ }
1615
+ title="Wallet on fire"
1616
+ detail="Every single request billed. It stacked up fast."
1617
+ />
1618
+ </div>
1619
+
1620
+ {/* Bottom verdict */}
1621
+ <div
1622
+ style={{
1623
+ position: "absolute",
1624
+ bottom: 10,
1625
+ width: "100%",
1626
+ textAlign: "center",
1627
+ opacity: interpolate(f, [8 * fps, 9 * fps], [0, 1], {
1628
+ extrapolateLeft: "clamp",
1629
+ extrapolateRight: "clamp",
1630
+ }),
1631
+ }}
1632
+ >
1633
+ <div
1634
+ style={{
1635
+ fontFamily: FONT,
1636
+ fontSize: 38,
1637
+ fontWeight: 700,
1638
+ color: C.fgSoft,
1639
+ letterSpacing: "-0.025em",
1640
+ fontStyle: "italic",
1641
+ }}
1642
+ >
1643
+ Not great.
1644
+ </div>
1645
+ </div>
1646
+ </SafeZone>
1647
+ </AbsoluteFill>
1648
+ );
1649
+ };
1650
+
1651
+ // ═══════════════════════════════════════════════════════════════
1652
+ // SCENE 6 — DISPATCH SOLVES IT (51.5-62.5s)
1653
+ // ═══════════════════════════════════════════════════════════════
1654
+
1655
+ const SolutionRow: React.FC<{
1656
+ idx: number;
1657
+ icon: React.ReactNode;
1658
+ title: string;
1659
+ detail: string;
1660
+ f: number;
1661
+ fps: number;
1662
+ baseDelay: number;
1663
+ }> = ({ idx, icon, title, detail, f, fps, baseDelay }) => {
1664
+ const delay = baseDelay + idx * 0.55;
1665
+ const s = gsapSpring(f, fps, delay, "snappy");
1666
+ const x = interpolate(s, [0, 1], [-120, 0]);
1667
+ const o = interpolate(s, [0, 0.4], [0, 1], {
1668
+ extrapolateLeft: "clamp",
1669
+ extrapolateRight: "clamp",
1670
+ });
1671
+ // Check mark draws after card arrives
1672
+ const checkDraw = interpolate(f, [(delay + 0.3) * fps, (delay + 0.9) * fps], [0, 1], {
1673
+ extrapolateLeft: "clamp",
1674
+ extrapolateRight: "clamp",
1675
+ });
1676
+
1677
+ return (
1678
+ <div
1679
+ style={{
1680
+ padding: "22px 28px",
1681
+ background: `linear-gradient(135deg, rgba(79,196,106,0.08), rgba(212,102,58,0.04))`,
1682
+ border: `1px solid rgba(79,196,106,0.25)`,
1683
+ borderLeftColor: C.claude,
1684
+ borderLeftWidth: 4,
1685
+ borderRadius: 16,
1686
+ display: "flex",
1687
+ alignItems: "center",
1688
+ gap: 20,
1689
+ opacity: o,
1690
+ transform: `translateX(${x}px)`,
1691
+ position: "relative",
1692
+ overflow: "hidden",
1693
+ }}
1694
+ >
1695
+ <div
1696
+ style={{
1697
+ width: 64,
1698
+ height: 64,
1699
+ borderRadius: 14,
1700
+ background: `rgba(79,196,106,0.15)`,
1701
+ border: `1px solid rgba(79,196,106,0.3)`,
1702
+ display: "flex",
1703
+ alignItems: "center",
1704
+ justifyContent: "center",
1705
+ flexShrink: 0,
1706
+ }}
1707
+ >
1708
+ {icon}
1709
+ </div>
1710
+ <div style={{ flex: 1 }}>
1711
+ <div
1712
+ style={{
1713
+ fontFamily: FONT,
1714
+ fontSize: 32,
1715
+ fontWeight: 700,
1716
+ color: C.fg,
1717
+ letterSpacing: "-0.025em",
1718
+ lineHeight: 1.1,
1719
+ }}
1720
+ >
1721
+ {title}
1722
+ </div>
1723
+ <div
1724
+ style={{
1725
+ fontFamily: FONT,
1726
+ fontSize: 18,
1727
+ fontWeight: 400,
1728
+ color: C.fgMuted,
1729
+ marginTop: 4,
1730
+ letterSpacing: "-0.01em",
1731
+ }}
1732
+ >
1733
+ {detail}
1734
+ </div>
1735
+ </div>
1736
+ {/* Checkmark */}
1737
+ <div
1738
+ style={{
1739
+ width: 56,
1740
+ height: 56,
1741
+ borderRadius: "50%",
1742
+ background: C.safe,
1743
+ display: "flex",
1744
+ alignItems: "center",
1745
+ justifyContent: "center",
1746
+ flexShrink: 0,
1747
+ boxShadow: `0 0 20px rgba(79,196,106,0.4)`,
1748
+ }}
1749
+ >
1750
+ <svg width="30" height="30" viewBox="0 0 24 24" fill="none">
1751
+ <path
1752
+ d="M5 12.5 10 17 20 7"
1753
+ stroke="#0a0a0b"
1754
+ strokeWidth="3.5"
1755
+ strokeLinecap="round"
1756
+ strokeLinejoin="round"
1757
+ strokeDasharray="30"
1758
+ strokeDashoffset={30 - checkDraw * 30}
1759
+ />
1760
+ </svg>
1761
+ </div>
1762
+ </div>
1763
+ );
1764
+ };
1765
+
1766
+ const SolutionScene: React.FC<SP> = ({ frame, fps }) => {
1767
+ const op = so(frame, fps, SEGS[5].s, SEGS[5].e);
1768
+ if (op === 0) return null;
1769
+ const f = lf(frame, fps, SEGS[5].s);
1770
+
1771
+ // Video inset — smaller, top-right
1772
+ const videoSpring = gsapSpring(f, fps, 0.3, "glass");
1773
+ const videoScale = interpolate(videoSpring, [0, 1], [0.85, 1]);
1774
+ const videoOp = interpolate(videoSpring, [0, 0.4], [0, 1], {
1775
+ extrapolateLeft: "clamp",
1776
+ extrapolateRight: "clamp",
1777
+ });
1778
+
1779
+ return (
1780
+ <AbsoluteFill style={{ opacity: op }}>
1781
+ <SafeZone>
1782
+ {/* Header */}
1783
+ <div
1784
+ style={{
1785
+ position: "absolute",
1786
+ top: 15,
1787
+ left: "50%",
1788
+ transform: "translate(-50%, 0)",
1789
+ padding: "8px 20px",
1790
+ background: C.claudeDim,
1791
+ border: `1px solid ${C.claude}`,
1792
+ borderRadius: 9999,
1793
+ fontFamily: MONO,
1794
+ fontSize: 16,
1795
+ fontWeight: 500,
1796
+ color: C.claudeSoft,
1797
+ letterSpacing: "0.2em",
1798
+ opacity: interpolate(f, [0.1 * fps, 0.6 * fps], [0, 1], {
1799
+ extrapolateLeft: "clamp",
1800
+ extrapolateRight: "clamp",
1801
+ }),
1802
+ }}
1803
+ >
1804
+ DISPATCH · HOW IT'S DIFFERENT
1805
+ </div>
1806
+
1807
+ {/* Title */}
1808
+ <div
1809
+ style={{
1810
+ position: "absolute",
1811
+ top: 80,
1812
+ width: "100%",
1813
+ textAlign: "center",
1814
+ padding: "0 48px",
1815
+ }}
1816
+ >
1817
+ <div
1818
+ style={{
1819
+ fontFamily: FONT,
1820
+ fontSize: 60,
1821
+ fontWeight: 700,
1822
+ color: C.fg,
1823
+ letterSpacing: "-0.035em",
1824
+ lineHeight: 1.05,
1825
+ opacity: interpolate(f, [0.3 * fps, 0.9 * fps], [0, 1], {
1826
+ extrapolateLeft: "clamp",
1827
+ extrapolateRight: "clamp",
1828
+ }),
1829
+ }}
1830
+ >
1831
+ Dispatch fixes{" "}
1832
+ <span style={{ color: C.claude }}>all of it.</span>
1833
+ </div>
1834
+ </div>
1835
+
1836
+ {/* Dispatch clip — small inset */}
1837
+ <div
1838
+ style={{
1839
+ position: "absolute",
1840
+ top: 200,
1841
+ left: "50%",
1842
+ transform: `translate(-50%, 0) scale(${videoScale})`,
1843
+ opacity: videoOp,
1844
+ width: 820,
1845
+ height: 300,
1846
+ borderRadius: 18,
1847
+ overflow: "hidden",
1848
+ border: `1px solid ${C.borderLoud}`,
1849
+ boxShadow: [
1850
+ "0 30px 60px -15px rgba(0,0,0,0.6)",
1851
+ "0 0 40px rgba(212,102,58,0.1)",
1852
+ ].join(", "),
1853
+ background: "#000",
1854
+ }}
1855
+ >
1856
+ <Sequence from={Math.round(SEGS[5].s * fps)} durationInFrames={Math.round(11 * fps)}>
1857
+ {/* REFERENCE-STRIP: <OffthreadVideo> removed — bring your own clip */}
1858
+ </Sequence>
1859
+ {/* Gradient overlay for text legibility */}
1860
+ <div
1861
+ style={{
1862
+ position: "absolute",
1863
+ inset: 0,
1864
+ background:
1865
+ "linear-gradient(180deg, rgba(0,0,0,0.15), transparent 40%, transparent 70%, rgba(10,10,11,0.85))",
1866
+ pointerEvents: "none",
1867
+ }}
1868
+ />
1869
+ {/* Live chip */}
1870
+ <div
1871
+ style={{
1872
+ position: "absolute",
1873
+ top: 14,
1874
+ left: 14,
1875
+ padding: "5px 12px",
1876
+ background: "rgba(0,0,0,0.6)",
1877
+ backdropFilter: "blur(6px)",
1878
+ borderRadius: 9999,
1879
+ border: "1px solid rgba(255,255,255,0.15)",
1880
+ fontFamily: MONO,
1881
+ fontSize: 11,
1882
+ fontWeight: 600,
1883
+ color: C.claudeSoft,
1884
+ letterSpacing: "0.14em",
1885
+ }}
1886
+ >
1887
+ CLAUDE · DISPATCH
1888
+ </div>
1889
+ </div>
1890
+
1891
+ {/* Solution rows */}
1892
+ <div
1893
+ style={{
1894
+ position: "absolute",
1895
+ top: 540,
1896
+ left: 40,
1897
+ right: 40,
1898
+ display: "flex",
1899
+ flexDirection: "column",
1900
+ gap: 18,
1901
+ }}
1902
+ >
1903
+ <SolutionRow
1904
+ idx={0}
1905
+ baseDelay={1.5}
1906
+ f={f}
1907
+ fps={fps}
1908
+ icon={
1909
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none">
1910
+ <rect
1911
+ x="3"
1912
+ y="4"
1913
+ width="18"
1914
+ height="13"
1915
+ rx="2"
1916
+ stroke={C.safe}
1917
+ strokeWidth="2"
1918
+ />
1919
+ <path d="M8 20h8m-4-3v3" stroke={C.safe} strokeWidth="2" strokeLinecap="round" />
1920
+ <circle cx="12" cy="10.5" r="2" fill={C.safe} />
1921
+ </svg>
1922
+ }
1923
+ title="Runs on your machine"
1924
+ detail="No API keys. No cloud. Just local."
1925
+ />
1926
+ <SolutionRow
1927
+ idx={1}
1928
+ baseDelay={1.5}
1929
+ f={f}
1930
+ fps={fps}
1931
+ icon={
1932
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none">
1933
+ <path
1934
+ d="M12 3 4 7v5c0 4.5 3 8 8 9 5-1 8-4.5 8-9V7l-8-4Z"
1935
+ stroke={C.safe}
1936
+ strokeWidth="2"
1937
+ strokeLinejoin="round"
1938
+ />
1939
+ <path d="m9 12 2 2 4-4" stroke={C.safe} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
1940
+ </svg>
1941
+ }
1942
+ title="Asks before it acts"
1943
+ detail="Sensitive action? Prompts you first."
1944
+ />
1945
+ </div>
1946
+ </SafeZone>
1947
+ </AbsoluteFill>
1948
+ );
1949
+ };
1950
+
1951
+ // ═══════════════════════════════════════════════════════════════
1952
+ // SCENE 7 — WINS (62-68.5s) — Safer · Cheaper · Smoother
1953
+ // ═══════════════════════════════════════════════════════════════
1954
+
1955
+ const WinsScene: React.FC<SP> = ({ frame, fps }) => {
1956
+ const op = so(frame, fps, SEGS[6].s, SEGS[6].e);
1957
+ if (op === 0) return null;
1958
+ const f = lf(frame, fps, SEGS[6].s);
1959
+
1960
+ const words = [
1961
+ { t: "SAFER.", color: C.safe, delay: 0.2 },
1962
+ { t: "CHEAPER.", color: C.claude, delay: 1.0 },
1963
+ { t: "SMOOTHER.", color: C.fg, delay: 1.8 },
1964
+ ];
1965
+
1966
+ // OBSOLETE stamp
1967
+ const stampSpring = gsapSpring(f, fps, 3.4, "bouncy");
1968
+ const stampScale = interpolate(stampSpring, [0, 1], [3, 1]);
1969
+ const stampOp = interpolate(stampSpring, [0, 0.3], [0, 1], {
1970
+ extrapolateLeft: "clamp",
1971
+ extrapolateRight: "clamp",
1972
+ });
1973
+ const stampRot = interpolate(stampSpring, [0, 1], [-25, -8]);
1974
+
1975
+ // OpenClaw fades at stamp moment
1976
+ const clawFade = interpolate(f, [3.6 * fps, 4.4 * fps], [0.75, 0.15], {
1977
+ extrapolateLeft: "clamp",
1978
+ extrapolateRight: "clamp",
1979
+ });
1980
+
1981
+ return (
1982
+ <AbsoluteFill style={{ opacity: op }}>
1983
+ <SafeZone>
1984
+ {/* Three giant words stacked */}
1985
+ <div
1986
+ style={{
1987
+ position: "absolute",
1988
+ top: 40,
1989
+ width: "100%",
1990
+ textAlign: "center",
1991
+ padding: "0 40px",
1992
+ }}
1993
+ >
1994
+ {words.map((w) => {
1995
+ const s = gsapSpring(f, fps, w.delay, "bouncy");
1996
+ const scale = interpolate(s, [0, 1], [0.6, 1]);
1997
+ const o = interpolate(s, [0, 0.3], [0, 1], {
1998
+ extrapolateLeft: "clamp",
1999
+ extrapolateRight: "clamp",
2000
+ });
2001
+ return (
2002
+ <div
2003
+ key={w.t}
2004
+ style={{
2005
+ fontFamily: FONT,
2006
+ fontSize: 128,
2007
+ fontWeight: 700,
2008
+ color: w.color,
2009
+ letterSpacing: "-0.055em",
2010
+ lineHeight: 1.04,
2011
+ transform: `scale(${scale})`,
2012
+ opacity: o,
2013
+ textShadow:
2014
+ w.color === C.claude
2015
+ ? "0 0 60px rgba(212,102,58,0.4)"
2016
+ : w.color === C.safe
2017
+ ? "0 0 60px rgba(79,196,106,0.3)"
2018
+ : "none",
2019
+ }}
2020
+ >
2021
+ {w.t}
2022
+ </div>
2023
+ );
2024
+ })}
2025
+ </div>
2026
+
2027
+ {/* OpenClaw at bottom, fading with stamp */}
2028
+ <div
2029
+ style={{
2030
+ position: "absolute",
2031
+ bottom: 10,
2032
+ left: "50%",
2033
+ transform: "translate(-50%, 0)",
2034
+ opacity: interpolate(f, [3 * fps, 3.5 * fps], [0, 1], {
2035
+ extrapolateLeft: "clamp",
2036
+ extrapolateRight: "clamp",
2037
+ }),
2038
+ }}
2039
+ >
2040
+ <div style={{ position: "relative", display: "flex", alignItems: "center", gap: 16 }}>
2041
+ <Img
2042
+ src={staticFile("captures/your-asset.png" /* REFERENCE-STRIP */)}
2043
+ style={{
2044
+ width: 72,
2045
+ height: 72,
2046
+ opacity: clawFade,
2047
+ filter: "grayscale(0.8)",
2048
+ }}
2049
+ />
2050
+ <div
2051
+ style={{
2052
+ fontFamily: FONT,
2053
+ fontSize: 34,
2054
+ fontWeight: 600,
2055
+ color: C.fgMuted,
2056
+ opacity: clawFade + 0.3,
2057
+ letterSpacing: "-0.02em",
2058
+ }}
2059
+ >
2060
+ OpenClaw workflows
2061
+ </div>
2062
+ {/* OBSOLETE stamp */}
2063
+ <div
2064
+ style={{
2065
+ position: "absolute",
2066
+ top: "50%",
2067
+ left: "50%",
2068
+ transform: `translate(-50%, -50%) scale(${stampScale}) rotate(${stampRot}deg)`,
2069
+ opacity: stampOp,
2070
+ padding: "8px 24px",
2071
+ border: `4px solid ${C.danger}`,
2072
+ borderRadius: 4,
2073
+ fontFamily: FONT,
2074
+ fontSize: 44,
2075
+ fontWeight: 700,
2076
+ color: C.danger,
2077
+ letterSpacing: "0.08em",
2078
+ textTransform: "uppercase",
2079
+ background: "rgba(10,10,11,0.5)",
2080
+ textShadow: `0 0 20px ${C.danger}`,
2081
+ whiteSpace: "nowrap",
2082
+ }}
2083
+ >
2084
+ OBSOLETE
2085
+ </div>
2086
+ </div>
2087
+ </div>
2088
+ </SafeZone>
2089
+ </AbsoluteFill>
2090
+ );
2091
+ };
2092
+
2093
+ // ═══════════════════════════════════════════════════════════════
2094
+ // SCENE 8 — CTA (68-74s) — Comment "Claude" + Like
2095
+ // ═══════════════════════════════════════════════════════════════
2096
+
2097
+ const CTAScene: React.FC<SP> = ({ frame, fps }) => {
2098
+ const op = so(frame, fps, SEGS[7].s, SEGS[7].e, 0.4, 0.6);
2099
+ if (op === 0) return null;
2100
+ const f = lf(frame, fps, SEGS[7].s);
2101
+
2102
+ // Phone mockup scale-in
2103
+ const phoneSpring = gsapSpring(f, fps, 0, "glass");
2104
+ const phoneScale = interpolate(phoneSpring, [0, 1], [0.85, 1]);
2105
+ const phoneOp = interpolate(phoneSpring, [0, 0.4], [0, 1], {
2106
+ extrapolateLeft: "clamp",
2107
+ extrapolateRight: "clamp",
2108
+ });
2109
+
2110
+ // "Claude" typing in comment
2111
+ const chars = Math.floor(
2112
+ interpolate(f, [1.2 * fps, 2.6 * fps], [0, 6], {
2113
+ extrapolateLeft: "clamp",
2114
+ extrapolateRight: "clamp",
2115
+ }),
2116
+ );
2117
+ const commentText = "Claude".slice(0, chars);
2118
+ const cursorVis = Math.floor(f / 10) % 2 === 0;
2119
+
2120
+ // Heart pulse & particle burst at 3.2s
2121
+ const heartSpring = gsapSpring(f, fps, 3.0, "bouncy");
2122
+ const heartScale = interpolate(heartSpring, [0, 1], [1, 1.4]);
2123
+ const heartSettle = interpolate(f, [3.6 * fps, 4.2 * fps], [1.4, 1.1], {
2124
+ extrapolateLeft: "clamp",
2125
+ extrapolateRight: "clamp",
2126
+ });
2127
+ const finalHeartScale = f < 3.6 * fps ? heartScale : heartSettle;
2128
+
2129
+ const particleT = interpolate(f, [3.0 * fps, 4.0 * fps], [0, 1], {
2130
+ extrapolateLeft: "clamp",
2131
+ extrapolateRight: "clamp",
2132
+ });
2133
+
2134
+ // Guide pill slide up at 4.2s
2135
+ const pillSpring = gsapSpring(f, fps, 4.0, "glass");
2136
+ const pillY = interpolate(pillSpring, [0, 1], [60, 0]);
2137
+ const pillOp = interpolate(pillSpring, [0, 0.3], [0, 1], {
2138
+ extrapolateLeft: "clamp",
2139
+ extrapolateRight: "clamp",
2140
+ });
2141
+
2142
+ return (
2143
+ <AbsoluteFill style={{ opacity: op }}>
2144
+ <SafeZone>
2145
+ {/* Instruction title */}
2146
+ <div
2147
+ style={{
2148
+ position: "absolute",
2149
+ top: 10,
2150
+ width: "100%",
2151
+ textAlign: "center",
2152
+ padding: "0 40px",
2153
+ }}
2154
+ >
2155
+ <div
2156
+ style={{
2157
+ fontFamily: FONT,
2158
+ fontSize: 58,
2159
+ fontWeight: 700,
2160
+ color: C.fg,
2161
+ letterSpacing: "-0.035em",
2162
+ lineHeight: 1.1,
2163
+ opacity: interpolate(f, [0.2 * fps, 0.8 * fps], [0, 1], {
2164
+ extrapolateLeft: "clamp",
2165
+ extrapolateRight: "clamp",
2166
+ }),
2167
+ }}
2168
+ >
2169
+ Drop{" "}
2170
+ <span
2171
+ style={{
2172
+ color: C.claude,
2173
+ background: `${C.claudeDim}`,
2174
+ padding: "4px 14px",
2175
+ borderRadius: 8,
2176
+ border: `1px solid ${C.claude}`,
2177
+ }}
2178
+ >
2179
+ Claude
2180
+ </span>{" "}
2181
+ in comments
2182
+ </div>
2183
+ <div
2184
+ style={{
2185
+ fontFamily: FONT,
2186
+ fontSize: 26,
2187
+ fontWeight: 400,
2188
+ color: C.fgMuted,
2189
+ marginTop: 16,
2190
+ letterSpacing: "-0.015em",
2191
+ opacity: interpolate(f, [0.8 * fps, 1.4 * fps], [0, 1], {
2192
+ extrapolateLeft: "clamp",
2193
+ extrapolateRight: "clamp",
2194
+ }),
2195
+ }}
2196
+ >
2197
+ + like for the full setup guide.
2198
+ </div>
2199
+ </div>
2200
+
2201
+ {/* Phone preview */}
2202
+ <div
2203
+ style={{
2204
+ position: "absolute",
2205
+ top: 220,
2206
+ left: "50%",
2207
+ transform: `translate(-50%, 0) scale(${phoneScale})`,
2208
+ opacity: phoneOp,
2209
+ }}
2210
+ >
2211
+ <PhoneFrame scale={0.9}>
2212
+ <div
2213
+ style={{
2214
+ padding: "54px 18px 0",
2215
+ display: "flex",
2216
+ flexDirection: "column",
2217
+ gap: 14,
2218
+ }}
2219
+ >
2220
+ {/* IG post header */}
2221
+ <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
2222
+ <div
2223
+ style={{
2224
+ width: 40,
2225
+ height: 40,
2226
+ borderRadius: "50%",
2227
+ background: `conic-gradient(from 45deg, ${C.claude}, #f0a659, ${C.claude}, ${C.claudeSoft})`,
2228
+ padding: 2,
2229
+ }}
2230
+ >
2231
+ <div
2232
+ style={{
2233
+ width: "100%",
2234
+ height: "100%",
2235
+ borderRadius: "50%",
2236
+ background: "#0a0a0b",
2237
+ display: "flex",
2238
+ alignItems: "center",
2239
+ justifyContent: "center",
2240
+ fontFamily: FONT,
2241
+ fontWeight: 700,
2242
+ fontSize: 15,
2243
+ color: C.claude,
2244
+ }}
2245
+ >
2246
+ D
2247
+ </div>
2248
+ </div>
2249
+ <div style={{ flex: 1 }}>
2250
+ <div style={{ fontFamily: FONT, fontSize: 13, fontWeight: 600, color: C.fg }}>
2251
+ devini.io
2252
+ </div>
2253
+ <div style={{ fontFamily: FONT, fontSize: 10, color: C.fgDim }}>
2254
+ Claude Dispatch · 2m
2255
+ </div>
2256
+ </div>
2257
+ </div>
2258
+
2259
+ {/* Comment field */}
2260
+ <div
2261
+ style={{
2262
+ marginTop: 8,
2263
+ padding: "12px 14px",
2264
+ background: "rgba(255,255,255,0.04)",
2265
+ border: "1px solid rgba(255,255,255,0.08)",
2266
+ borderRadius: 12,
2267
+ display: "flex",
2268
+ alignItems: "center",
2269
+ gap: 10,
2270
+ }}
2271
+ >
2272
+ <div
2273
+ style={{
2274
+ width: 26,
2275
+ height: 26,
2276
+ borderRadius: "50%",
2277
+ background: "rgba(255,255,255,0.08)",
2278
+ flexShrink: 0,
2279
+ }}
2280
+ />
2281
+ <div
2282
+ style={{
2283
+ flex: 1,
2284
+ fontFamily: FONT,
2285
+ fontSize: 14,
2286
+ color: commentText ? C.fg : C.fgDim,
2287
+ fontWeight: 500,
2288
+ minHeight: 18,
2289
+ }}
2290
+ >
2291
+ {commentText || "Add a comment..."}
2292
+ {commentText && commentText.length < 6 && cursorVis && "|"}
2293
+ </div>
2294
+ <div
2295
+ style={{
2296
+ fontFamily: FONT,
2297
+ fontSize: 12,
2298
+ fontWeight: 700,
2299
+ color: chars === 6 ? C.claude : C.fgDim,
2300
+ letterSpacing: "-0.01em",
2301
+ }}
2302
+ >
2303
+ Post
2304
+ </div>
2305
+ </div>
2306
+
2307
+ {/* Action row with like */}
2308
+ <div
2309
+ style={{
2310
+ marginTop: 6,
2311
+ display: "flex",
2312
+ alignItems: "center",
2313
+ gap: 18,
2314
+ padding: "8px 2px",
2315
+ }}
2316
+ >
2317
+ {/* Heart with particles */}
2318
+ <div style={{ position: "relative" }}>
2319
+ <svg
2320
+ width={42}
2321
+ height={42}
2322
+ viewBox="0 0 24 24"
2323
+ fill={f > 3.0 * fps ? C.danger : "none"}
2324
+ stroke={f > 3.0 * fps ? C.danger : C.fg}
2325
+ strokeWidth="2"
2326
+ style={{
2327
+ transform: `scale(${finalHeartScale})`,
2328
+ transition: "transform 0.1s ease",
2329
+ }}
2330
+ >
2331
+ <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />
2332
+ </svg>
2333
+ {/* Particles */}
2334
+ {[0, 1, 2, 3, 4, 5].map((i) => {
2335
+ const angle = (i / 6) * Math.PI * 2;
2336
+ const dist = particleT * 60;
2337
+ const px = Math.cos(angle) * dist;
2338
+ const py = Math.sin(angle) * dist;
2339
+ const pOp = interpolate(particleT, [0, 0.2, 1], [0, 1, 0], {
2340
+ extrapolateLeft: "clamp",
2341
+ extrapolateRight: "clamp",
2342
+ });
2343
+ return (
2344
+ <div
2345
+ key={i}
2346
+ style={{
2347
+ position: "absolute",
2348
+ top: 20,
2349
+ left: 20,
2350
+ width: 6,
2351
+ height: 6,
2352
+ borderRadius: "50%",
2353
+ background: i % 2 === 0 ? C.danger : C.claude,
2354
+ transform: `translate(${px}px, ${py}px) scale(${1 - particleT * 0.5})`,
2355
+ opacity: pOp,
2356
+ }}
2357
+ />
2358
+ );
2359
+ })}
2360
+ </div>
2361
+ <svg width={38} height={38} viewBox="0 0 24 24" fill="none" stroke={C.fg} strokeWidth="2">
2362
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
2363
+ </svg>
2364
+ <svg width={38} height={38} viewBox="0 0 24 24" fill="none" stroke={C.fg} strokeWidth="2">
2365
+ <path d="m22 2-7 20-4-9-9-4Z" strokeLinejoin="round" strokeLinecap="round" />
2366
+ </svg>
2367
+ <div style={{ flex: 1 }} />
2368
+ <svg width={38} height={38} viewBox="0 0 24 24" fill="none" stroke={C.fg} strokeWidth="2">
2369
+ <path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2Z" strokeLinejoin="round" />
2370
+ </svg>
2371
+ </div>
2372
+ </div>
2373
+ </PhoneFrame>
2374
+ </div>
2375
+
2376
+ {/* Setup guide pill at bottom */}
2377
+ <div
2378
+ style={{
2379
+ position: "absolute",
2380
+ bottom: 10,
2381
+ left: "50%",
2382
+ transform: `translate(-50%, ${pillY}px)`,
2383
+ opacity: pillOp,
2384
+ padding: "18px 32px",
2385
+ background: `linear-gradient(135deg, ${C.claude}, ${C.claudeSoft})`,
2386
+ borderRadius: 9999,
2387
+ display: "flex",
2388
+ alignItems: "center",
2389
+ gap: 14,
2390
+ boxShadow: `0 20px 40px -10px rgba(212,102,58,0.5), 0 0 60px rgba(212,102,58,0.3)`,
2391
+ }}
2392
+ >
2393
+ <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.5">
2394
+ <path
2395
+ d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"
2396
+ strokeLinejoin="round"
2397
+ strokeLinecap="round"
2398
+ />
2399
+ </svg>
2400
+ <span
2401
+ style={{
2402
+ fontFamily: FONT,
2403
+ fontSize: 26,
2404
+ fontWeight: 700,
2405
+ color: "#fff",
2406
+ letterSpacing: "-0.02em",
2407
+ }}
2408
+ >
2409
+ I'll DM the setup guide
2410
+ </span>
2411
+ </div>
2412
+ </SafeZone>
2413
+ </AbsoluteFill>
2414
+ );
2415
+ };
2416
+
2417
+ // ═══════════════════════════════════════════════════════════════
2418
+ // MAIN COMPOSITION
2419
+ // ═══════════════════════════════════════════════════════════════
2420
+
2421
+ export const ClaudeDispatchReel: React.FC = () => {
2422
+ const frame = useCurrentFrame();
2423
+ const { fps } = useVideoConfig();
2424
+
2425
+ return (
2426
+ <AbsoluteFill style={{ backgroundColor: C.bg, fontFamily: FONT }}>
2427
+ {/* REFERENCE-STRIP: <Audio> tag removed — bring your own voiceover */}
2428
+
2429
+ <Background frame={frame} fps={fps} />
2430
+
2431
+ <HookScene frame={frame} fps={fps} />
2432
+ <IdeaScene frame={frame} fps={fps} />
2433
+ <ExampleScene frame={frame} fps={fps} />
2434
+ <WhyScene frame={frame} fps={fps} />
2435
+ <IssuesScene frame={frame} fps={fps} />
2436
+ <SolutionScene frame={frame} fps={fps} />
2437
+ <WinsScene frame={frame} fps={fps} />
2438
+ <CTAScene frame={frame} fps={fps} />
2439
+ </AbsoluteFill>
2440
+ );
2441
+ };