@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,2218 @@
1
+ /**
2
+ * REFERENCE — PaperclipReel (Family: glass)
3
+ *
4
+ * Canonical example of the glass 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/PaperclipReel.tsx
15
+ * Bundled at: 2026-05-08T18:50:39.501Z
16
+ */
17
+ import React from "react";
18
+ import {
19
+ AbsoluteFill,
20
+ Audio,
21
+ Img,
22
+ OffthreadVideo,
23
+ Sequence,
24
+ interpolate,
25
+ spring,
26
+ staticFile,
27
+ useCurrentFrame,
28
+ useVideoConfig,
29
+ } from "remotion";
30
+ import {
31
+ ease,
32
+ C,
33
+ FONT,
34
+ MONO,
35
+ glassBase,
36
+ CausticBlobs,
37
+ HairlineGrid,
38
+ EyebrowPill,
39
+ StaggeredWords,
40
+ Counter,
41
+ SonarRings,
42
+ ParticleBurst,
43
+ LightBeam,
44
+ FloatingGlyphs,
45
+ } from "./GraphifyReel";
46
+
47
+ // ═══════════════════════════════════════════════════════════════
48
+ // TIMING — 55.0s @ 30fps = 1650 frames, audio-locked from whisper SRT
49
+ // ═══════════════════════════════════════════════════════════════
50
+ export const PAPERCLIP_TOTAL = 1650;
51
+
52
+ export const BEAT = {
53
+ hook: 0, // 0.0s
54
+ replaced: 180, // 6.0s — "...just replaced the entire company"
55
+ notOpenAI: 240, // 8.0s — "And it's not OpenAI, it's not Google"
56
+ reveal: 270, // 9.0s — "It's called Paperclip"
57
+ license: 330, // 11.0s — fully open source, MIT licensed
58
+ companies: 410, // 13.7s — "people are already spinning up whole companies"
59
+ notFramework: 488, // 16.3s — "this isn't another agent framework"
60
+ demoStart: 595, // 19.8s — "you open paperclip..." [paperclip.mp4 begins]
61
+ demoEnd: 1410, // 47.0s — paperclip.mp4 ends [815f = 27.17s, NO CUTS]
62
+ notVibe: 1410, // 47.0s — "This isn't vibe-coding a chatbot"
63
+ fromPhone: 1454, // 48.5s — "running an entire company from your phone"
64
+ cta: 1561, // 52.0s — "Comment AI" CTA
65
+ end: 1650, // 55.0s
66
+ } as const;
67
+
68
+ export const DEMO_DURATION = BEAT.demoEnd - BEAT.demoStart; // 815 frames = 27.17s — must match paperclip.mp4
69
+
70
+ const SAFE_TOP = 290;
71
+ const SAFE_BOTTOM = 1500;
72
+
73
+ // ═══════════════════════════════════════════════════════════════
74
+ // PAPERCLIP GLYPH — inline SVG, single-path with iridescent stroke draw-in
75
+ // ═══════════════════════════════════════════════════════════════
76
+ export const PaperclipGlyph: React.FC<{ size: number; startFrame: number; rotate?: number }> = ({
77
+ size,
78
+ startFrame,
79
+ rotate = -10,
80
+ }) => {
81
+ const frame = useCurrentFrame();
82
+ const local = frame - startFrame;
83
+ const dashOffset = interpolate(local, [0, 28], [520, 0], {
84
+ extrapolateLeft: "clamp",
85
+ extrapolateRight: "clamp",
86
+ easing: ease.expoOut,
87
+ });
88
+ const op = interpolate(local, [0, 12], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
89
+ return (
90
+ <svg
91
+ width={size}
92
+ height={size}
93
+ viewBox="0 0 200 200"
94
+ style={{ transform: `rotate(${rotate}deg)`, opacity: op, willChange: "transform, opacity" }}
95
+ >
96
+ <defs>
97
+ <linearGradient id="papclipIri" x1="0" y1="0" x2="1" y2="1">
98
+ <stop offset="0%" stopColor={C.iriCyan} />
99
+ <stop offset="50%" stopColor={C.iriViolet} />
100
+ <stop offset="100%" stopColor={C.iriRose} />
101
+ </linearGradient>
102
+ </defs>
103
+ <path
104
+ d="M 70 30 L 70 145 A 30 30 0 0 0 130 145 L 130 55 A 18 18 0 0 0 94 55 L 94 130"
105
+ fill="none"
106
+ stroke="url(#papclipIri)"
107
+ strokeWidth="14"
108
+ strokeLinecap="round"
109
+ strokeLinejoin="round"
110
+ strokeDasharray="520"
111
+ strokeDashoffset={dashOffset}
112
+ />
113
+ </svg>
114
+ );
115
+ };
116
+
117
+
118
+ // ═══════════════════════════════════════════════════════════════
119
+ // ORG CHART CARD — glass card with role + dot + name
120
+ // ═══════════════════════════════════════════════════════════════
121
+ export const OrgChartCard: React.FC<{
122
+ role: string;
123
+ title: string;
124
+ dotColor: string;
125
+ startFrame: number;
126
+ width?: number;
127
+ height?: number;
128
+ }> = ({ role, title, dotColor, startFrame, width = 360, height = 130 }) => {
129
+ const frame = useCurrentFrame();
130
+ const local = frame - startFrame;
131
+ const op = interpolate(local, [0, 18], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
132
+ const x = interpolate(local, [0, 28], [80, 0], {
133
+ extrapolateLeft: "clamp",
134
+ extrapolateRight: "clamp",
135
+ easing: ease.power3Out,
136
+ });
137
+ const scale = spring({
138
+ frame: local,
139
+ fps: 30,
140
+ config: { damping: 11, stiffness: 110 },
141
+ from: 0.85,
142
+ to: 1,
143
+ });
144
+ return (
145
+ <div
146
+ style={{
147
+ ...glassBase,
148
+ background: "rgba(255,255,255,0.92)",
149
+ width,
150
+ height,
151
+ borderRadius: 22,
152
+ padding: "20px 24px",
153
+ display: "flex",
154
+ flexDirection: "column",
155
+ justifyContent: "center",
156
+ gap: 10,
157
+ opacity: op,
158
+ transform: `translateX(${x}px) scale(${scale})`,
159
+ willChange: "transform, opacity",
160
+ boxShadow: [
161
+ "0 24px 48px -12px rgba(120,100,180,0.30)",
162
+ "0 8px 16px -4px rgba(120,100,180,0.18)",
163
+ "inset 0 1.5px 0 rgba(255,255,255,1)",
164
+ "inset 0 -1px 0 rgba(255,255,255,0.4)",
165
+ ].join(", "),
166
+ }}
167
+ >
168
+ <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
169
+ <div
170
+ style={{
171
+ width: 10,
172
+ height: 10,
173
+ borderRadius: "50%",
174
+ background: dotColor,
175
+ boxShadow: `0 0 14px ${dotColor}`,
176
+ }}
177
+ />
178
+ <div
179
+ style={{
180
+ fontFamily: MONO,
181
+ fontSize: 14,
182
+ fontWeight: 600,
183
+ color: C.inkSoft,
184
+ letterSpacing: "0.18em",
185
+ textTransform: "uppercase",
186
+ }}
187
+ >
188
+ {role}
189
+ </div>
190
+ </div>
191
+ <div
192
+ style={{
193
+ fontFamily: FONT,
194
+ fontSize: 30,
195
+ fontWeight: 800,
196
+ color: C.ink,
197
+ letterSpacing: "-0.025em",
198
+ lineHeight: 1.05,
199
+ }}
200
+ >
201
+ {title}
202
+ </div>
203
+ </div>
204
+ );
205
+ };
206
+
207
+ // ═══════════════════════════════════════════════════════════════
208
+ // TOOL CHIP — glass pill with logo + label, used for Claude Code/Codex/Cursor
209
+ // ═══════════════════════════════════════════════════════════════
210
+ export const ToolChip: React.FC<{
211
+ label: string;
212
+ iconPath: string;
213
+ iconBg?: string;
214
+ startFrame: number;
215
+ }> = ({ label, iconPath, iconBg, startFrame }) => {
216
+ const frame = useCurrentFrame();
217
+ const local = frame - startFrame;
218
+ const op = interpolate(local, [0, 16], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
219
+ const y = interpolate(local, [0, 28], [40, 0], {
220
+ extrapolateLeft: "clamp",
221
+ extrapolateRight: "clamp",
222
+ easing: ease.power3Out,
223
+ });
224
+ const scale = spring({
225
+ frame: local,
226
+ fps: 30,
227
+ config: { damping: 10, stiffness: 130 },
228
+ from: 0.8,
229
+ to: 1,
230
+ });
231
+ return (
232
+ <div
233
+ style={{
234
+ ...glassBase,
235
+ borderRadius: 9999,
236
+ padding: "14px 26px 14px 16px",
237
+ display: "inline-flex",
238
+ alignItems: "center",
239
+ gap: 14,
240
+ opacity: op,
241
+ transform: `translateY(${y}px) scale(${scale})`,
242
+ willChange: "transform, opacity",
243
+ }}
244
+ >
245
+ <div
246
+ style={{
247
+ width: 38,
248
+ height: 38,
249
+ borderRadius: 12,
250
+ background: iconBg ?? "rgba(255,255,255,0.6)",
251
+ display: "flex",
252
+ alignItems: "center",
253
+ justifyContent: "center",
254
+ padding: 6,
255
+ border: "1px solid rgba(255,255,255,0.85)",
256
+ }}
257
+ >
258
+ <Img src={staticFile("captures/your-asset.png" /* REFERENCE-STRIP */)} style={{ width: "100%", height: "100%", objectFit: "contain" }} />
259
+ </div>
260
+ <div
261
+ style={{
262
+ fontFamily: FONT,
263
+ fontSize: 22,
264
+ fontWeight: 700,
265
+ color: C.ink,
266
+ letterSpacing: "-0.01em",
267
+ }}
268
+ >
269
+ {label}
270
+ </div>
271
+ </div>
272
+ );
273
+ };
274
+
275
+ // ═══════════════════════════════════════════════════════════════
276
+ // DATA TILE — glass card with mono label + bold value
277
+ // ═══════════════════════════════════════════════════════════════
278
+ export const DataTile: React.FC<{
279
+ label: string;
280
+ value: string;
281
+ dotColor: string;
282
+ startFrame: number;
283
+ }> = ({ label, value, dotColor, startFrame }) => {
284
+ const frame = useCurrentFrame();
285
+ const local = frame - startFrame;
286
+ const op = interpolate(local, [0, 18], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
287
+ const y = interpolate(local, [0, 32], [120, 0], {
288
+ extrapolateLeft: "clamp",
289
+ extrapolateRight: "clamp",
290
+ easing: ease.expoOut,
291
+ });
292
+ return (
293
+ <div
294
+ style={{
295
+ ...glassBase,
296
+ borderRadius: 22,
297
+ padding: "22px 28px",
298
+ display: "flex",
299
+ flexDirection: "column",
300
+ gap: 8,
301
+ minWidth: 280,
302
+ opacity: op,
303
+ transform: `translateY(${y}px)`,
304
+ willChange: "transform, opacity",
305
+ }}
306
+ >
307
+ <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
308
+ <div
309
+ style={{
310
+ width: 8,
311
+ height: 8,
312
+ borderRadius: "50%",
313
+ background: dotColor,
314
+ boxShadow: `0 0 12px ${dotColor}`,
315
+ }}
316
+ />
317
+ <div
318
+ style={{
319
+ fontFamily: MONO,
320
+ fontSize: 13,
321
+ fontWeight: 500,
322
+ color: C.inkMuted,
323
+ letterSpacing: "0.2em",
324
+ textTransform: "uppercase",
325
+ }}
326
+ >
327
+ {label}
328
+ </div>
329
+ </div>
330
+ <div
331
+ style={{
332
+ fontFamily: FONT,
333
+ fontSize: 32,
334
+ fontWeight: 700,
335
+ color: C.ink,
336
+ letterSpacing: "-0.025em",
337
+ lineHeight: 1,
338
+ }}
339
+ >
340
+ {value}
341
+ </div>
342
+ </div>
343
+ );
344
+ };
345
+
346
+ // ═══════════════════════════════════════════════════════════════
347
+ // DEVICE CARD — glass-bezel container for the OffthreadVideo
348
+ // Header strip with macOS traffic-light dots + breadcrumb
349
+ // ═══════════════════════════════════════════════════════════════
350
+ export const DeviceCard: React.FC<{
351
+ width: number;
352
+ height: number;
353
+ translateX: number;
354
+ scale: number;
355
+ opacity: number;
356
+ children: React.ReactNode;
357
+ }> = ({ width, height, translateX, scale, opacity, children }) => {
358
+ return (
359
+ <div
360
+ style={{
361
+ position: "absolute",
362
+ left: "50%",
363
+ top: "50%",
364
+ width,
365
+ height,
366
+ transform: `translate(-50%, -50%) translateX(${translateX}px) scale(${scale})`,
367
+ opacity,
368
+ willChange: "transform, opacity",
369
+ }}
370
+ >
371
+ <div
372
+ style={{
373
+ ...glassBase,
374
+ width: "100%",
375
+ height: "100%",
376
+ borderRadius: 36,
377
+ padding: 14,
378
+ display: "flex",
379
+ flexDirection: "column",
380
+ }}
381
+ >
382
+ {/* Window controls */}
383
+ <div style={{ display: "flex", gap: 8, padding: "4px 8px 12px", alignItems: "center" }}>
384
+ <div style={{ width: 12, height: 12, borderRadius: 6, background: "#FF5F57" }} />
385
+ <div style={{ width: 12, height: 12, borderRadius: 6, background: "#FEBC2E" }} />
386
+ <div style={{ width: 12, height: 12, borderRadius: 6, background: "#28C840" }} />
387
+ <div
388
+ style={{
389
+ marginLeft: "auto",
390
+ fontFamily: MONO,
391
+ fontSize: 13,
392
+ color: C.inkDim,
393
+ letterSpacing: "0.05em",
394
+ }}
395
+ >
396
+ paperclip ▸ ai
397
+ </div>
398
+ </div>
399
+ {/* Video frame */}
400
+ <div
401
+ style={{
402
+ flex: 1,
403
+ borderRadius: 22,
404
+ overflow: "hidden",
405
+ background: "#0E0E12",
406
+ position: "relative",
407
+ }}
408
+ >
409
+ {children}
410
+ </div>
411
+ </div>
412
+ </div>
413
+ );
414
+ };
415
+
416
+ // ═══════════════════════════════════════════════════════════════
417
+ // SCENE 1 — HOOK (0–180, 6.0s) — counter opener
418
+ // "This, over 61,000 stars on GitHub project just replaced the entire company."
419
+ // ═══════════════════════════════════════════════════════════════
420
+ const HookScene: React.FC = () => {
421
+ const frame = useCurrentFrame();
422
+
423
+ // Eyebrow pill
424
+ const eyebrowOp = interpolate(frame, [6, 26], [0, 1], {
425
+ extrapolateLeft: "clamp",
426
+ extrapolateRight: "clamp",
427
+ });
428
+
429
+ // Counter container scale-in
430
+ const counterScale = spring({
431
+ frame: frame - 4,
432
+ fps: 30,
433
+ config: { damping: 14, stiffness: 130 },
434
+ from: 0.7,
435
+ to: 1,
436
+ });
437
+ const counterBlur = interpolate(frame, [4, 30], [16, 0], {
438
+ extrapolateLeft: "clamp",
439
+ extrapolateRight: "clamp",
440
+ easing: ease.expoOut,
441
+ });
442
+
443
+ // Pulse on "stars" (frame 76)
444
+ const starsPulse = interpolate(frame, [76, 80, 88], [1, 1.03, 1], {
445
+ extrapolateLeft: "clamp",
446
+ extrapolateRight: "clamp",
447
+ });
448
+
449
+ return (
450
+ <AbsoluteFill>
451
+ {/* Layer 1: sonar rings (perpetual) */}
452
+ <SonarRings />
453
+
454
+ {/* Layer 2: light beam sweeps */}
455
+ <LightBeam delay={0} angle={-18} />
456
+ <LightBeam delay={26} angle={22} />
457
+ <LightBeam delay={60} angle={-12} />
458
+
459
+ {/* Layer 3: floating glass glyphs */}
460
+ <FloatingGlyphs />
461
+
462
+ {/* Layer 4: particle burst */}
463
+ <ParticleBurst />
464
+
465
+ {/* Eyebrow pill: "STARS ON GITHUB" */}
466
+ <div
467
+ style={{
468
+ position: "absolute",
469
+ top: SAFE_TOP + 40,
470
+ left: 0,
471
+ right: 0,
472
+ display: "flex",
473
+ justifyContent: "center",
474
+ opacity: eyebrowOp,
475
+ }}
476
+ >
477
+ <EyebrowPill>STARS ON GITHUB</EyebrowPill>
478
+ </div>
479
+
480
+ {/* Counter — center stage, 220px Geist Bold, tabular-nums */}
481
+ <AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
482
+ <div
483
+ style={{
484
+ transform: `scale(${counterScale * starsPulse})`,
485
+ filter: `blur(${counterBlur}px)`,
486
+ willChange: "transform, filter",
487
+ }}
488
+ >
489
+ <Counter
490
+ from={0}
491
+ to={61800}
492
+ startFrame={8}
493
+ duration={56}
494
+ easing={ease.expoOut}
495
+ format={(n) => Math.round(n).toLocaleString("en-US")}
496
+ style={{
497
+ display: "inline-block",
498
+ fontFamily: FONT,
499
+ fontSize: 220,
500
+ fontWeight: 800,
501
+ color: C.ink,
502
+ letterSpacing: "-0.07em",
503
+ lineHeight: 0.9,
504
+ fontVariantNumeric: "tabular-nums",
505
+ background: `linear-gradient(135deg, ${C.iriCyan}, ${C.iriViolet}, ${C.iriRose})`,
506
+ WebkitBackgroundClip: "text",
507
+ WebkitTextFillColor: "transparent",
508
+ backgroundClip: "text",
509
+ }}
510
+ />
511
+ {/* Star + label */}
512
+ <div
513
+ style={{
514
+ marginTop: 28,
515
+ display: "flex",
516
+ alignItems: "center",
517
+ justifyContent: "center",
518
+ gap: 14,
519
+ opacity: interpolate(frame, [50, 80], [0, 1], {
520
+ extrapolateLeft: "clamp",
521
+ extrapolateRight: "clamp",
522
+ }),
523
+ }}
524
+ >
525
+ <svg width="34" height="34" viewBox="0 0 24 24" fill={C.iriGold}>
526
+ <path d="M12 2 L14.9 8.6 L22 9.3 L16.6 14.1 L18.2 21 L12 17.3 L5.8 21 L7.4 14.1 L2 9.3 L9.1 8.6 Z" />
527
+ </svg>
528
+ <div
529
+ style={{
530
+ fontFamily: MONO,
531
+ fontSize: 26,
532
+ fontWeight: 500,
533
+ color: C.inkSoft,
534
+ letterSpacing: "0.16em",
535
+ textTransform: "uppercase",
536
+ }}
537
+ >
538
+ stars · paperclip on github
539
+ </div>
540
+ </div>
541
+ </div>
542
+ </AbsoluteFill>
543
+
544
+ {/* Sub-headline that lands during silence after "GitHub" */}
545
+ <div
546
+ style={{
547
+ position: "absolute",
548
+ left: 80,
549
+ right: 80,
550
+ bottom: 1920 - SAFE_BOTTOM + 80,
551
+ textAlign: "center",
552
+ opacity: interpolate(frame, [110, 138], [0, 1], {
553
+ extrapolateLeft: "clamp",
554
+ extrapolateRight: "clamp",
555
+ }),
556
+ }}
557
+ >
558
+ <StaggeredWords
559
+ text="just replaced the entire company."
560
+ startFrame={120}
561
+ fontSize={62}
562
+ fontWeight={600}
563
+ color={C.inkSoft}
564
+ letterSpacing="-0.03em"
565
+ align="center"
566
+ highlight="company"
567
+ highlightColor={C.iriRose}
568
+ />
569
+ </div>
570
+ </AbsoluteFill>
571
+ );
572
+ };
573
+
574
+ // ═══════════════════════════════════════════════════════════════
575
+ // SCENE 2 — REPLACED (180–240, 2.0s) — building tiles get crossed out
576
+ // ═══════════════════════════════════════════════════════════════
577
+ const ReplacedScene: React.FC = () => {
578
+ const frame = useCurrentFrame();
579
+ const local = frame - BEAT.replaced;
580
+
581
+ const headlineOp = interpolate(local, [0, 22], [0, 1], { extrapolateRight: "clamp" });
582
+
583
+ return (
584
+ <AbsoluteFill>
585
+ <FloatingGlyphs />
586
+
587
+ {/* Eyebrow */}
588
+ <div
589
+ style={{
590
+ position: "absolute",
591
+ top: SAFE_TOP + 30,
592
+ left: 0,
593
+ right: 0,
594
+ display: "flex",
595
+ justifyContent: "center",
596
+ opacity: interpolate(local, [0, 16], [0, 1], { extrapolateRight: "clamp" }),
597
+ }}
598
+ >
599
+ <EyebrowPill>WHAT THIS DOES</EyebrowPill>
600
+ </div>
601
+
602
+ {/* Headline */}
603
+ <div
604
+ style={{
605
+ position: "absolute",
606
+ top: SAFE_TOP + 130,
607
+ left: 80,
608
+ right: 80,
609
+ opacity: headlineOp,
610
+ }}
611
+ >
612
+ <StaggeredWords
613
+ text="It just replaced the entire"
614
+ startFrame={BEAT.replaced + 4}
615
+ fontSize={88}
616
+ fontWeight={800}
617
+ color={C.ink}
618
+ align="left"
619
+ />
620
+ <div style={{ height: 6 }} />
621
+ <StaggeredWords
622
+ text="company."
623
+ startFrame={BEAT.replaced + 28}
624
+ fontSize={120}
625
+ fontWeight={800}
626
+ color={C.iriRose}
627
+ align="left"
628
+ letterSpacing="-0.05em"
629
+ />
630
+ </div>
631
+
632
+ {/* Three building tiles getting struck out */}
633
+ <div
634
+ style={{
635
+ position: "absolute",
636
+ left: 0,
637
+ right: 0,
638
+ top: 1100,
639
+ display: "flex",
640
+ gap: 24,
641
+ justifyContent: "center",
642
+ alignItems: "center",
643
+ }}
644
+ >
645
+ {[
646
+ { label: "Engineering", strikeAt: 195 },
647
+ { label: "Marketing", strikeAt: 205 },
648
+ { label: "Operations", strikeAt: 215 },
649
+ ].map((b, i) => {
650
+ const cardLocal = frame - (BEAT.replaced + 6 + i * 6);
651
+ const op = interpolate(cardLocal, [0, 18], [0, 1], { extrapolateRight: "clamp" });
652
+ const y = interpolate(cardLocal, [0, 24], [40, 0], {
653
+ extrapolateRight: "clamp",
654
+ easing: ease.power3Out,
655
+ });
656
+ const struckLocal = frame - (b.strikeAt + 12);
657
+ const dimOp = interpolate(struckLocal, [0, 12], [1, 0.45], { extrapolateRight: "clamp" });
658
+ return (
659
+ <div
660
+ key={i}
661
+ style={{
662
+ ...glassBase,
663
+ width: 280,
664
+ height: 200,
665
+ borderRadius: 24,
666
+ padding: 24,
667
+ display: "flex",
668
+ flexDirection: "column",
669
+ justifyContent: "flex-end",
670
+ gap: 10,
671
+ opacity: op * dimOp,
672
+ transform: `translateY(${y}px)`,
673
+ position: "relative",
674
+ willChange: "transform, opacity",
675
+ }}
676
+ >
677
+ {/* Building icon */}
678
+ <svg
679
+ width="56"
680
+ height="56"
681
+ viewBox="0 0 24 24"
682
+ fill="none"
683
+ stroke={C.inkMuted}
684
+ strokeWidth="1.4"
685
+ style={{ position: "absolute", top: 24, left: 24 }}
686
+ >
687
+ <rect x="4" y="3" width="16" height="18" rx="1" />
688
+ <line x1="9" y1="7" x2="9" y2="7" />
689
+ <line x1="9" y1="11" x2="9" y2="11" />
690
+ <line x1="9" y1="15" x2="9" y2="15" />
691
+ <line x1="15" y1="7" x2="15" y2="7" />
692
+ <line x1="15" y1="11" x2="15" y2="11" />
693
+ <line x1="15" y1="15" x2="15" y2="15" />
694
+ <line x1="10" y1="21" x2="14" y2="21" />
695
+ </svg>
696
+ <div
697
+ style={{
698
+ fontFamily: FONT,
699
+ fontSize: 26,
700
+ fontWeight: 700,
701
+ color: C.ink,
702
+ letterSpacing: "-0.02em",
703
+ }}
704
+ >
705
+ {b.label}
706
+ </div>
707
+ <div
708
+ style={{
709
+ fontFamily: MONO,
710
+ fontSize: 13,
711
+ color: C.inkDim,
712
+ letterSpacing: "0.16em",
713
+ textTransform: "uppercase",
714
+ }}
715
+ >
716
+ Department
717
+ </div>
718
+ </div>
719
+ );
720
+ })}
721
+ </div>
722
+ </AbsoluteFill>
723
+ );
724
+ };
725
+
726
+ // ═══════════════════════════════════════════════════════════════
727
+ // SCENE 3 — NOT OPENAI / GOOGLE (240–270, 1.0s)
728
+ // "And it's not OpenAI, it's not Google."
729
+ // ═══════════════════════════════════════════════════════════════
730
+ const NotOpenAIScene: React.FC = () => {
731
+ const frame = useCurrentFrame();
732
+ const local = frame - BEAT.notOpenAI;
733
+
734
+ const headOp = interpolate(local, [0, 14], [0, 1], { extrapolateRight: "clamp" });
735
+ const headY = interpolate(local, [0, 24], [30, 0], {
736
+ extrapolateRight: "clamp",
737
+ easing: ease.power3Out,
738
+ });
739
+
740
+ const cardScale1 = spring({
741
+ frame: local - 2,
742
+ fps: 30,
743
+ config: { damping: 12, stiffness: 130 },
744
+ from: 0.85,
745
+ to: 1,
746
+ });
747
+ const cardScale2 = spring({
748
+ frame: local - 8,
749
+ fps: 30,
750
+ config: { damping: 12, stiffness: 130 },
751
+ from: 0.85,
752
+ to: 1,
753
+ });
754
+
755
+ return (
756
+ <AbsoluteFill>
757
+ <FloatingGlyphs />
758
+
759
+ {/* Headline */}
760
+ <div
761
+ style={{
762
+ position: "absolute",
763
+ top: SAFE_TOP + 80,
764
+ left: 80,
765
+ right: 80,
766
+ textAlign: "center",
767
+ opacity: headOp,
768
+ transform: `translateY(${headY}px)`,
769
+ }}
770
+ >
771
+ <div
772
+ style={{
773
+ fontFamily: MONO,
774
+ fontSize: 22,
775
+ fontWeight: 500,
776
+ color: C.inkMuted,
777
+ letterSpacing: "0.22em",
778
+ textTransform: "uppercase",
779
+ marginBottom: 16,
780
+ }}
781
+ >
782
+ and it's
783
+ </div>
784
+ <div
785
+ style={{
786
+ fontFamily: FONT,
787
+ fontSize: 110,
788
+ fontWeight: 800,
789
+ color: C.ink,
790
+ letterSpacing: "-0.04em",
791
+ lineHeight: 1,
792
+ }}
793
+ >
794
+ not these.
795
+ </div>
796
+ </div>
797
+
798
+ {/* Two big logo cards */}
799
+ <div
800
+ style={{
801
+ position: "absolute",
802
+ left: 0,
803
+ right: 0,
804
+ top: 1000,
805
+ display: "flex",
806
+ gap: 30,
807
+ justifyContent: "center",
808
+ }}
809
+ >
810
+ {/* OpenAI */}
811
+ <div
812
+ style={{
813
+ ...glassBase,
814
+ width: 380,
815
+ height: 380,
816
+ borderRadius: 32,
817
+ padding: 60,
818
+ display: "flex",
819
+ flexDirection: "column",
820
+ alignItems: "center",
821
+ justifyContent: "center",
822
+ gap: 18,
823
+ position: "relative",
824
+ transform: `scale(${cardScale1})`,
825
+ willChange: "transform",
826
+ }}
827
+ >
828
+ <Img
829
+ src={staticFile("captures/your-asset.png" /* REFERENCE-STRIP */)}
830
+ style={{ width: 160, height: 160, objectFit: "contain" }}
831
+ />
832
+ <div
833
+ style={{
834
+ fontFamily: FONT,
835
+ fontSize: 30,
836
+ fontWeight: 700,
837
+ color: C.ink,
838
+ letterSpacing: "-0.02em",
839
+ }}
840
+ >
841
+ OpenAI
842
+ </div>
843
+ </div>
844
+
845
+ {/* Google */}
846
+ <div
847
+ style={{
848
+ ...glassBase,
849
+ width: 380,
850
+ height: 380,
851
+ borderRadius: 32,
852
+ padding: 60,
853
+ display: "flex",
854
+ flexDirection: "column",
855
+ alignItems: "center",
856
+ justifyContent: "center",
857
+ gap: 18,
858
+ position: "relative",
859
+ transform: `scale(${cardScale2})`,
860
+ willChange: "transform",
861
+ }}
862
+ >
863
+ <Img
864
+ src={staticFile("captures/your-asset.png" /* REFERENCE-STRIP */)}
865
+ style={{ width: 160, height: 160, objectFit: "contain" }}
866
+ />
867
+ <div
868
+ style={{
869
+ fontFamily: FONT,
870
+ fontSize: 30,
871
+ fontWeight: 700,
872
+ color: C.ink,
873
+ letterSpacing: "-0.02em",
874
+ }}
875
+ >
876
+ Google
877
+ </div>
878
+ </div>
879
+ </div>
880
+ </AbsoluteFill>
881
+ );
882
+ };
883
+
884
+ // ═══════════════════════════════════════════════════════════════
885
+ // SCENE 4 — REVEAL (270–330, 2.0s) — "It's called Paperclip"
886
+ // ═══════════════════════════════════════════════════════════════
887
+ const RevealScene: React.FC = () => {
888
+ const frame = useCurrentFrame();
889
+ const local = frame - BEAT.reveal;
890
+
891
+ const wordmarkScale = spring({
892
+ frame: local - 10,
893
+ fps: 30,
894
+ config: { damping: 11, stiffness: 120 },
895
+ from: 0.85,
896
+ to: 1,
897
+ });
898
+ const wordmarkOp = interpolate(local, [10, 30], [0, 1], { extrapolateRight: "clamp" });
899
+
900
+ return (
901
+ <AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
902
+ {/* Particle burst sparkle */}
903
+ <ParticleBurst />
904
+
905
+ {/* Eyebrow */}
906
+ <div
907
+ style={{
908
+ position: "absolute",
909
+ top: SAFE_TOP + 60,
910
+ left: 0,
911
+ right: 0,
912
+ display: "flex",
913
+ justifyContent: "center",
914
+ opacity: interpolate(local, [0, 18], [0, 1], { extrapolateRight: "clamp" }),
915
+ }}
916
+ >
917
+ <EyebrowPill>IT'S CALLED</EyebrowPill>
918
+ </div>
919
+
920
+ {/* Glyph + wordmark */}
921
+ <div
922
+ style={{
923
+ display: "flex",
924
+ alignItems: "center",
925
+ gap: 28,
926
+ transform: `scale(${wordmarkScale})`,
927
+ willChange: "transform",
928
+ }}
929
+ >
930
+ <PaperclipGlyph size={210} startFrame={BEAT.reveal} rotate={-12} />
931
+ <div
932
+ style={{
933
+ fontFamily: FONT,
934
+ fontSize: 180,
935
+ fontWeight: 800,
936
+ letterSpacing: "-0.05em",
937
+ lineHeight: 0.9,
938
+ opacity: wordmarkOp,
939
+ background: `linear-gradient(135deg, ${C.iriCyan}, ${C.iriViolet}, ${C.iriRose})`,
940
+ WebkitBackgroundClip: "text",
941
+ WebkitTextFillColor: "transparent",
942
+ backgroundClip: "text",
943
+ }}
944
+ >
945
+ Paperclip
946
+ </div>
947
+ </div>
948
+
949
+ {/* Tag underneath */}
950
+ <div
951
+ style={{
952
+ marginTop: 24,
953
+ fontFamily: MONO,
954
+ fontSize: 22,
955
+ fontWeight: 500,
956
+ color: C.inkSoft,
957
+ letterSpacing: "0.18em",
958
+ textTransform: "uppercase",
959
+ opacity: interpolate(local, [30, 50], [0, 1], { extrapolateRight: "clamp" }),
960
+ }}
961
+ >
962
+ an org-chart that hires itself
963
+ </div>
964
+ </AbsoluteFill>
965
+ );
966
+ };
967
+
968
+ // ═══════════════════════════════════════════════════════════════
969
+ // SCENE 5 — LICENSE (330–410, 2.7s) — MIT / Open Source / 61.8k★
970
+ // ═══════════════════════════════════════════════════════════════
971
+ const LicenseScene: React.FC = () => {
972
+ const frame = useCurrentFrame();
973
+ const local = frame - BEAT.license;
974
+
975
+ const pills = [
976
+ { label: "MIT LICENSED", dot: C.iriCyan, delay: 10, icon: null },
977
+ { label: "OPEN SOURCE", dot: C.iriViolet, delay: 18, icon: null },
978
+ { label: "61.8K STARS", dot: C.iriGold, delay: 26, icon: "icons/github.svg" },
979
+ ];
980
+
981
+ return (
982
+ <AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
983
+ <FloatingGlyphs />
984
+
985
+ {/* Headline */}
986
+ <div
987
+ style={{
988
+ position: "absolute",
989
+ top: SAFE_TOP + 80,
990
+ left: 80,
991
+ right: 80,
992
+ textAlign: "center",
993
+ opacity: interpolate(local, [4, 30], [0, 1], { extrapolateRight: "clamp" }),
994
+ }}
995
+ >
996
+ <StaggeredWords
997
+ text="Already powering real businesses."
998
+ startFrame={BEAT.license + 4}
999
+ fontSize={72}
1000
+ fontWeight={700}
1001
+ color={C.ink}
1002
+ align="center"
1003
+ highlight="real"
1004
+ highlightColor={C.iriViolet}
1005
+ />
1006
+ </div>
1007
+
1008
+ {/* Three glass pills cascading in vertically */}
1009
+ <div
1010
+ style={{
1011
+ display: "flex",
1012
+ flexDirection: "column",
1013
+ gap: 22,
1014
+ alignItems: "center",
1015
+ marginTop: 80,
1016
+ }}
1017
+ >
1018
+ {pills.map((p, i) => {
1019
+ const pillLocal = frame - (BEAT.license + p.delay);
1020
+ const op = interpolate(pillLocal, [0, 18], [0, 1], { extrapolateRight: "clamp" });
1021
+ const y = interpolate(pillLocal, [0, 28], [60, 0], {
1022
+ extrapolateRight: "clamp",
1023
+ easing: ease.expoOut,
1024
+ });
1025
+ const scale = spring({
1026
+ frame: pillLocal,
1027
+ fps: 30,
1028
+ config: { damping: 11, stiffness: 110 },
1029
+ from: 0.9,
1030
+ to: 1,
1031
+ });
1032
+ return (
1033
+ <div
1034
+ key={i}
1035
+ style={{
1036
+ ...glassBase,
1037
+ borderRadius: 9999,
1038
+ padding: "20px 36px",
1039
+ display: "inline-flex",
1040
+ alignItems: "center",
1041
+ gap: 16,
1042
+ opacity: op,
1043
+ transform: `translateY(${y}px) scale(${scale})`,
1044
+ willChange: "transform, opacity",
1045
+ }}
1046
+ >
1047
+ <div
1048
+ style={{
1049
+ width: 12,
1050
+ height: 12,
1051
+ borderRadius: "50%",
1052
+ background: p.dot,
1053
+ boxShadow: `0 0 18px ${p.dot}`,
1054
+ }}
1055
+ />
1056
+ {p.icon && (
1057
+ <Img
1058
+ src={staticFile("captures/your-asset.png" /* REFERENCE-STRIP */)}
1059
+ style={{ width: 26, height: 26, objectFit: "contain" }}
1060
+ />
1061
+ )}
1062
+ <div
1063
+ style={{
1064
+ fontFamily: MONO,
1065
+ fontSize: 26,
1066
+ fontWeight: 600,
1067
+ color: C.ink,
1068
+ letterSpacing: "0.14em",
1069
+ }}
1070
+ >
1071
+ {p.label}
1072
+ </div>
1073
+ </div>
1074
+ );
1075
+ })}
1076
+ </div>
1077
+ </AbsoluteFill>
1078
+ );
1079
+ };
1080
+
1081
+ // ═══════════════════════════════════════════════════════════════
1082
+ // SCENE 6 — COMPANIES (410–488, 2.6s) — "spinning up whole companies"
1083
+ // ═══════════════════════════════════════════════════════════════
1084
+ const CompaniesScene: React.FC = () => {
1085
+ const frame = useCurrentFrame();
1086
+ const local = frame - BEAT.companies;
1087
+
1088
+ const companies = [
1089
+ { name: "Halcyon Studio", role: "Brand & Web", x: -340, dot: C.iriCyan, delay: 10 },
1090
+ { name: "Atlas Foundry", role: "Hardware Lab", x: 0, dot: C.iriViolet, delay: 18 },
1091
+ { name: "Meridian Labs", role: "AI Research", x: 340, dot: C.iriRose, delay: 26 },
1092
+ ];
1093
+
1094
+ return (
1095
+ <AbsoluteFill>
1096
+ <FloatingGlyphs />
1097
+
1098
+ {/* Headline */}
1099
+ <div
1100
+ style={{
1101
+ position: "absolute",
1102
+ top: SAFE_TOP + 60,
1103
+ left: 80,
1104
+ right: 80,
1105
+ textAlign: "center",
1106
+ opacity: interpolate(local, [0, 24], [0, 1], { extrapolateRight: "clamp" }),
1107
+ }}
1108
+ >
1109
+ <StaggeredWords
1110
+ text="People are spinning up whole"
1111
+ startFrame={BEAT.companies + 4}
1112
+ fontSize={72}
1113
+ fontWeight={600}
1114
+ color={C.inkSoft}
1115
+ align="center"
1116
+ />
1117
+ <div style={{ height: 6 }} />
1118
+ <StaggeredWords
1119
+ text="companies on it."
1120
+ startFrame={BEAT.companies + 22}
1121
+ fontSize={96}
1122
+ fontWeight={800}
1123
+ color={C.ink}
1124
+ align="center"
1125
+ highlight="companies"
1126
+ highlightColor={C.iriViolet}
1127
+ />
1128
+ </div>
1129
+
1130
+ {/* Three company cards drift into a row */}
1131
+ <div
1132
+ style={{
1133
+ position: "absolute",
1134
+ left: 0,
1135
+ right: 0,
1136
+ top: 1080,
1137
+ display: "flex",
1138
+ justifyContent: "center",
1139
+ alignItems: "center",
1140
+ }}
1141
+ >
1142
+ {companies.map((co, i) => {
1143
+ const coLocal = frame - (BEAT.companies + co.delay);
1144
+ const op = interpolate(coLocal, [0, 22], [0, 1], { extrapolateRight: "clamp" });
1145
+ // Drift from chaos to alignment
1146
+ const xOffset = interpolate(coLocal, [0, 32], [(i - 1) * 60, 0], {
1147
+ extrapolateRight: "clamp",
1148
+ easing: ease.backOut,
1149
+ });
1150
+ const yOffset = interpolate(coLocal, [0, 32], [80 - i * 30, 0], {
1151
+ extrapolateRight: "clamp",
1152
+ easing: ease.backOut,
1153
+ });
1154
+ const rot = interpolate(coLocal, [0, 32], [(i - 1) * 4, 0], {
1155
+ extrapolateRight: "clamp",
1156
+ easing: ease.expoOut,
1157
+ });
1158
+ return (
1159
+ <div
1160
+ key={i}
1161
+ style={{
1162
+ ...glassBase,
1163
+ position: "absolute",
1164
+ left: "50%",
1165
+ width: 300,
1166
+ height: 200,
1167
+ marginLeft: -150,
1168
+ borderRadius: 24,
1169
+ padding: 24,
1170
+ display: "flex",
1171
+ flexDirection: "column",
1172
+ justifyContent: "space-between",
1173
+ opacity: op,
1174
+ transform: `translateX(${co.x + xOffset}px) translateY(${yOffset}px) rotate(${rot}deg)`,
1175
+ willChange: "transform, opacity",
1176
+ }}
1177
+ >
1178
+ <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
1179
+ <div
1180
+ style={{
1181
+ width: 10,
1182
+ height: 10,
1183
+ borderRadius: "50%",
1184
+ background: co.dot,
1185
+ boxShadow: `0 0 14px ${co.dot}`,
1186
+ }}
1187
+ />
1188
+ <div
1189
+ style={{
1190
+ fontFamily: MONO,
1191
+ fontSize: 12,
1192
+ color: C.inkMuted,
1193
+ letterSpacing: "0.18em",
1194
+ textTransform: "uppercase",
1195
+ }}
1196
+ >
1197
+ built on paperclip
1198
+ </div>
1199
+ </div>
1200
+ <div>
1201
+ <div
1202
+ style={{
1203
+ fontFamily: FONT,
1204
+ fontSize: 28,
1205
+ fontWeight: 700,
1206
+ color: C.ink,
1207
+ letterSpacing: "-0.02em",
1208
+ lineHeight: 1.05,
1209
+ }}
1210
+ >
1211
+ {co.name}
1212
+ </div>
1213
+ <div
1214
+ style={{
1215
+ marginTop: 6,
1216
+ fontFamily: MONO,
1217
+ fontSize: 13,
1218
+ color: C.inkDim,
1219
+ letterSpacing: "0.12em",
1220
+ textTransform: "uppercase",
1221
+ }}
1222
+ >
1223
+ {co.role}
1224
+ </div>
1225
+ </div>
1226
+ </div>
1227
+ );
1228
+ })}
1229
+ </div>
1230
+ </AbsoluteFill>
1231
+ );
1232
+ };
1233
+
1234
+ // ═══════════════════════════════════════════════════════════════
1235
+ // SCENE 7 — NOT FRAMEWORK (488–595, 3.6s) — strikethrough list
1236
+ // ═══════════════════════════════════════════════════════════════
1237
+ const NotFrameworkScene: React.FC = () => {
1238
+ const frame = useCurrentFrame();
1239
+ const local = frame - BEAT.notFramework;
1240
+
1241
+ const frameworks = [
1242
+ { label: "LangChain", appearAt: 4, strikeAt: 22 },
1243
+ { label: "AutoGPT", appearAt: 12, strikeAt: 36 },
1244
+ { label: "CrewAI", appearAt: 20, strikeAt: 50 },
1245
+ { label: "Yet another framework", appearAt: 28, strikeAt: 64 },
1246
+ ];
1247
+
1248
+ return (
1249
+ <AbsoluteFill>
1250
+ <FloatingGlyphs />
1251
+
1252
+ {/* Eyebrow */}
1253
+ <div
1254
+ style={{
1255
+ position: "absolute",
1256
+ top: SAFE_TOP + 30,
1257
+ left: 0,
1258
+ right: 0,
1259
+ display: "flex",
1260
+ justifyContent: "center",
1261
+ opacity: interpolate(local, [0, 14], [0, 1], { extrapolateRight: "clamp" }),
1262
+ }}
1263
+ >
1264
+ <EyebrowPill>HERE'S THE THING</EyebrowPill>
1265
+ </div>
1266
+
1267
+ {/* List of crossed-out frameworks */}
1268
+ <div
1269
+ style={{
1270
+ position: "absolute",
1271
+ left: 100,
1272
+ right: 100,
1273
+ top: SAFE_TOP + 130,
1274
+ display: "flex",
1275
+ flexDirection: "column",
1276
+ gap: 14,
1277
+ }}
1278
+ >
1279
+ {frameworks.map((f, i) => {
1280
+ const fwLocal = frame - (BEAT.notFramework + f.appearAt);
1281
+ const op = interpolate(fwLocal, [0, 16], [0, 1], { extrapolateRight: "clamp" });
1282
+ const x = interpolate(fwLocal, [0, 24], [40, 0], {
1283
+ extrapolateRight: "clamp",
1284
+ easing: ease.power3Out,
1285
+ });
1286
+ const struckLocal = frame - (BEAT.notFramework + f.strikeAt + 14);
1287
+ const dim = interpolate(struckLocal, [0, 12], [1, 0.35], { extrapolateRight: "clamp" });
1288
+ return (
1289
+ <div
1290
+ key={i}
1291
+ style={{
1292
+ ...glassBase,
1293
+ width: 880,
1294
+ height: 84,
1295
+ borderRadius: 18,
1296
+ padding: "0 30px",
1297
+ display: "flex",
1298
+ alignItems: "center",
1299
+ gap: 20,
1300
+ opacity: op * dim,
1301
+ transform: `translateX(${x}px)`,
1302
+ position: "relative",
1303
+ willChange: "transform, opacity",
1304
+ }}
1305
+ >
1306
+ <div
1307
+ style={{
1308
+ fontFamily: MONO,
1309
+ fontSize: 18,
1310
+ fontWeight: 500,
1311
+ color: C.inkDim,
1312
+ letterSpacing: "0.18em",
1313
+ }}
1314
+ >
1315
+ {String(i + 1).padStart(2, "0")}
1316
+ </div>
1317
+ <div
1318
+ style={{
1319
+ fontFamily: FONT,
1320
+ fontSize: 36,
1321
+ fontWeight: 700,
1322
+ color: C.ink,
1323
+ letterSpacing: "-0.02em",
1324
+ }}
1325
+ >
1326
+ {f.label}
1327
+ </div>
1328
+ <div
1329
+ style={{
1330
+ marginLeft: "auto",
1331
+ fontFamily: MONO,
1332
+ fontSize: 14,
1333
+ color: C.inkMuted,
1334
+ letterSpacing: "0.16em",
1335
+ textTransform: "uppercase",
1336
+ }}
1337
+ >
1338
+ agent framework
1339
+ </div>
1340
+ </div>
1341
+ );
1342
+ })}
1343
+ </div>
1344
+
1345
+ {/* Punch headline below the list, lands at frame 575 (local 87) */}
1346
+ <div
1347
+ style={{
1348
+ position: "absolute",
1349
+ left: 80,
1350
+ right: 80,
1351
+ top: 1280,
1352
+ textAlign: "center",
1353
+ opacity: interpolate(local, [80, 100], [0, 1], { extrapolateRight: "clamp" }),
1354
+ transform: `translateY(${interpolate(local, [80, 100], [30, 0], {
1355
+ extrapolateRight: "clamp",
1356
+ easing: ease.expoOut,
1357
+ })}px)`,
1358
+ }}
1359
+ >
1360
+ <div
1361
+ style={{
1362
+ fontFamily: FONT,
1363
+ fontSize: 86,
1364
+ fontWeight: 800,
1365
+ color: C.ink,
1366
+ letterSpacing: "-0.04em",
1367
+ lineHeight: 0.95,
1368
+ }}
1369
+ >
1370
+ Paperclip is different.
1371
+ </div>
1372
+ <div
1373
+ style={{
1374
+ marginTop: 16,
1375
+ fontFamily: MONO,
1376
+ fontSize: 26,
1377
+ fontWeight: 500,
1378
+ color: C.iriViolet,
1379
+ letterSpacing: "0.2em",
1380
+ textTransform: "uppercase",
1381
+ }}
1382
+ >
1383
+ it hires.
1384
+ </div>
1385
+ </div>
1386
+ </AbsoluteFill>
1387
+ );
1388
+ };
1389
+
1390
+ // ═══════════════════════════════════════════════════════════════
1391
+ // SCENE 8 — DEMO (595–1410, 27.17s) — paperclip.mp4 UNCUT, 4 sub-acts
1392
+ // Sub-act A (Boot, 595–760): scale-in, CEO sonar pulse
1393
+ // Sub-act B (Roster, 760–1037): translateX -120, org cards stack right
1394
+ // Sub-act C (Tools, 1037–1265): translateX 0, iridescent ring + tool chips
1395
+ // Sub-act D (Discipline, 1265–1410): dim, data trio flies in
1396
+ // ═══════════════════════════════════════════════════════════════
1397
+ const DemoScene: React.FC = () => {
1398
+ const frame = useCurrentFrame();
1399
+ const local = frame - BEAT.demoStart;
1400
+
1401
+ // Device card scale-in (sub-act A)
1402
+ const bootScale = spring({
1403
+ frame: local,
1404
+ fps: 30,
1405
+ config: { damping: 14, stiffness: 130 },
1406
+ from: 0.92,
1407
+ to: 1,
1408
+ });
1409
+
1410
+ // Device stays centered through every sub-act (no translateX).
1411
+ const translateX = 0;
1412
+
1413
+ // Sub-act D: device dim at frame 1265 (local 670)
1414
+ const deviceOpacity = interpolate(local, [670, 700], [1, 0.85], {
1415
+ extrapolateLeft: "clamp",
1416
+ extrapolateRight: "clamp",
1417
+ });
1418
+
1419
+ // Org cards visible during sub-act B (frame 760 → local 165),
1420
+ // fade out cleanly when tool chips appear in sub-act C (frame 1037 → local 442)
1421
+ const orgCardOp = interpolate(local, [442, 470], [1, 0], {
1422
+ extrapolateLeft: "clamp",
1423
+ extrapolateRight: "clamp",
1424
+ });
1425
+
1426
+ // Sonar pulse on CEO spawn at frame 700 (local 105)
1427
+ const sonarLocal = frame - 700;
1428
+
1429
+ // Boot caption pop at frame 705 (local 110)
1430
+ const captionLocal = frame - 705;
1431
+ const captionOp = interpolate(captionLocal, [0, 16], [0, 1], { extrapolateRight: "clamp" });
1432
+ const captionY = interpolate(captionLocal, [0, 28], [30, 0], {
1433
+ extrapolateRight: "clamp",
1434
+ easing: ease.expoOut,
1435
+ });
1436
+ // Caption fades out at sub-act B kick-in (frame 760)
1437
+ const captionFadeOut = interpolate(frame, [752, 772], [1, 0], {
1438
+ extrapolateLeft: "clamp",
1439
+ extrapolateRight: "clamp",
1440
+ });
1441
+
1442
+ return (
1443
+ <AbsoluteFill>
1444
+ {/* Device card with the OffthreadVideo inside */}
1445
+ <DeviceCard
1446
+ width={960}
1447
+ height={760}
1448
+ translateX={translateX}
1449
+ scale={bootScale}
1450
+ opacity={deviceOpacity}
1451
+ >
1452
+ <Sequence from={BEAT.demoStart} durationInFrames={DEMO_DURATION}>
1453
+ {/* REFERENCE-STRIP: <OffthreadVideo> removed — bring your own clip */}
1454
+ </Sequence>
1455
+ </DeviceCard>
1456
+
1457
+ {/* Sub-act A: Sonar pulse anchored to device top-right area */}
1458
+ {frame >= 700 && frame < 760 && (
1459
+ <div
1460
+ style={{
1461
+ position: "absolute",
1462
+ left: "50%",
1463
+ top: "50%",
1464
+ transform: "translate(180px, -300px)",
1465
+ width: 240,
1466
+ height: 240,
1467
+ }}
1468
+ >
1469
+ {[0, 14].map((delay, i) => {
1470
+ const cycle = sonarLocal - delay;
1471
+ if (cycle < 0) return null;
1472
+ const ringScale = interpolate(cycle, [0, 50], [0.2, 1.4], {
1473
+ extrapolateRight: "clamp",
1474
+ easing: ease.expoOut,
1475
+ });
1476
+ const op = interpolate(cycle, [0, 12, 50], [0, 0.7, 0], {
1477
+ extrapolateRight: "clamp",
1478
+ });
1479
+ return (
1480
+ <div
1481
+ key={i}
1482
+ style={{
1483
+ position: "absolute",
1484
+ inset: 0,
1485
+ borderRadius: "50%",
1486
+ border: `2px solid ${C.iriViolet}`,
1487
+ transform: `scale(${ringScale})`,
1488
+ opacity: op,
1489
+ willChange: "transform, opacity",
1490
+ }}
1491
+ />
1492
+ );
1493
+ })}
1494
+ </div>
1495
+ )}
1496
+
1497
+ {/* Sub-act A: caption "CEO AGENT SPAWNED" */}
1498
+ {frame >= 705 && frame < 772 && (
1499
+ <div
1500
+ style={{
1501
+ position: "absolute",
1502
+ top: SAFE_TOP - 40,
1503
+ left: 0,
1504
+ right: 0,
1505
+ display: "flex",
1506
+ justifyContent: "center",
1507
+ opacity: captionOp * captionFadeOut,
1508
+ transform: `translateY(${captionY}px)`,
1509
+ }}
1510
+ >
1511
+ <EyebrowPill>CEO AGENT SPAWNED</EyebrowPill>
1512
+ </div>
1513
+ )}
1514
+
1515
+ {/* Sub-act B: roster as 2x2 grid ABOVE the centered device */}
1516
+ {frame >= 760 && frame < 1040 && (
1517
+ <div
1518
+ style={{
1519
+ position: "absolute",
1520
+ top: 1380,
1521
+ left: 80,
1522
+ right: 80,
1523
+ display: "grid",
1524
+ gridTemplateColumns: "1fr 1fr",
1525
+ gap: 16,
1526
+ opacity: orgCardOp,
1527
+ willChange: "opacity",
1528
+ }}
1529
+ >
1530
+ <OrgChartCard
1531
+ role="founder · 24/7"
1532
+ title="CEO Agent"
1533
+ dotColor={C.iriCyan}
1534
+ startFrame={760}
1535
+ width={460}
1536
+ height={104}
1537
+ />
1538
+ <OrgChartCard
1539
+ role="ic · 12 seats"
1540
+ title="Engineers"
1541
+ dotColor={C.iriViolet}
1542
+ startFrame={905}
1543
+ width={460}
1544
+ height={104}
1545
+ />
1546
+ <OrgChartCard
1547
+ role="growth lead"
1548
+ title="Marketing Director"
1549
+ dotColor={C.iriRose}
1550
+ startFrame={942}
1551
+ width={460}
1552
+ height={104}
1553
+ />
1554
+ <OrgChartCard
1555
+ role="content"
1556
+ title="Copywriter"
1557
+ dotColor={C.iriGold}
1558
+ startFrame={1009}
1559
+ width={460}
1560
+ height={104}
1561
+ />
1562
+ </div>
1563
+ )}
1564
+
1565
+ {/* Sub-act B: caption "HIRING ROSTER" */}
1566
+ {frame >= 770 && frame < 1040 && (
1567
+ <div
1568
+ style={{
1569
+ position: "absolute",
1570
+ top: SAFE_TOP - 40,
1571
+ left: 0,
1572
+ right: 0,
1573
+ display: "flex",
1574
+ justifyContent: "center",
1575
+ opacity: interpolate(frame, [770, 790, 1020, 1040], [0, 1, 1, 0], {
1576
+ extrapolateLeft: "clamp",
1577
+ extrapolateRight: "clamp",
1578
+ }),
1579
+ }}
1580
+ >
1581
+ <EyebrowPill>HIRING ROSTER · 24/7</EyebrowPill>
1582
+ </div>
1583
+ )}
1584
+
1585
+ {/* Sub-act C: three tool chips in a row above the device */}
1586
+ {frame >= 1037 && frame < 1290 && (
1587
+ <div
1588
+ style={{
1589
+ position: "absolute",
1590
+ top: 280,
1591
+ left: 0,
1592
+ right: 0,
1593
+ display: "flex",
1594
+ justifyContent: "center",
1595
+ gap: 16,
1596
+ flexWrap: "wrap",
1597
+ opacity: interpolate(frame, [1265, 1290], [1, 0], {
1598
+ extrapolateLeft: "clamp",
1599
+ extrapolateRight: "clamp",
1600
+ }),
1601
+ }}
1602
+ >
1603
+ <ToolChip
1604
+ label="Claude Code"
1605
+ iconPath="icons/claude.svg"
1606
+ iconBg="rgba(255,255,255,0.7)"
1607
+ startFrame={1068}
1608
+ />
1609
+ <ToolChip
1610
+ label="Codex"
1611
+ iconPath="icons/openai.svg"
1612
+ iconBg="rgba(255,255,255,0.7)"
1613
+ startFrame={1095}
1614
+ />
1615
+ <ToolChip
1616
+ label="Cursor"
1617
+ iconPath="icons/cursor.svg"
1618
+ iconBg="#0E0E12"
1619
+ startFrame={1115}
1620
+ />
1621
+ </div>
1622
+ )}
1623
+
1624
+ {/* Sub-act C: caption */}
1625
+ {frame >= 1040 && frame < 1290 && (
1626
+ <div
1627
+ style={{
1628
+ position: "absolute",
1629
+ top: SAFE_TOP - 40,
1630
+ left: 0,
1631
+ right: 0,
1632
+ display: "flex",
1633
+ justifyContent: "center",
1634
+ opacity: interpolate(frame, [1045, 1065, 1265, 1290], [0, 1, 1, 0], {
1635
+ extrapolateLeft: "clamp",
1636
+ extrapolateRight: "clamp",
1637
+ }),
1638
+ }}
1639
+ >
1640
+ <EyebrowPill>REAL AI AGENTS</EyebrowPill>
1641
+ </div>
1642
+ )}
1643
+
1644
+ {/* Sub-act D: data trio at the bottom */}
1645
+ {frame >= 1265 && (
1646
+ <div
1647
+ style={{
1648
+ position: "absolute",
1649
+ left: 0,
1650
+ right: 0,
1651
+ bottom: 200,
1652
+ display: "flex",
1653
+ justifyContent: "center",
1654
+ gap: 18,
1655
+ }}
1656
+ >
1657
+ <DataTile
1658
+ label="Budget"
1659
+ value="$200/mo"
1660
+ dotColor={C.iriGold}
1661
+ startFrame={1270}
1662
+ />
1663
+ <DataTile
1664
+ label="Job Title"
1665
+ value="CEO"
1666
+ dotColor={C.iriViolet}
1667
+ startFrame={1276}
1668
+ />
1669
+ <DataTile
1670
+ label="Reports To"
1671
+ value="→ You"
1672
+ dotColor={C.iriCyan}
1673
+ startFrame={1282}
1674
+ />
1675
+ </div>
1676
+ )}
1677
+
1678
+ {/* Sub-act D: caption */}
1679
+ {frame >= 1265 && (
1680
+ <div
1681
+ style={{
1682
+ position: "absolute",
1683
+ top: SAFE_TOP - 40,
1684
+ left: 0,
1685
+ right: 0,
1686
+ display: "flex",
1687
+ justifyContent: "center",
1688
+ opacity: interpolate(frame, [1270, 1290], [0, 1], { extrapolateRight: "clamp" }),
1689
+ }}
1690
+ >
1691
+ <EyebrowPill>EVERY AGENT IS ACCOUNTABLE</EyebrowPill>
1692
+ </div>
1693
+ )}
1694
+ </AbsoluteFill>
1695
+ );
1696
+ };
1697
+
1698
+ // ═══════════════════════════════════════════════════════════════
1699
+ // SCENE 9 — NOT VIBE (1410–1454, 1.5s) — strikethrough on "vibe-coding"
1700
+ // ═══════════════════════════════════════════════════════════════
1701
+ const NotVibeScene: React.FC = () => {
1702
+ const frame = useCurrentFrame();
1703
+ const local = frame - BEAT.notVibe;
1704
+
1705
+ const op = interpolate(local, [0, 18], [0, 1], { extrapolateRight: "clamp" });
1706
+ const y = interpolate(local, [0, 24], [30, 0], {
1707
+ extrapolateRight: "clamp",
1708
+ easing: ease.expoOut,
1709
+ });
1710
+
1711
+ return (
1712
+ <AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
1713
+ <FloatingGlyphs />
1714
+
1715
+ <div
1716
+ style={{
1717
+ position: "relative",
1718
+ textAlign: "center",
1719
+ opacity: op,
1720
+ transform: `translateY(${y}px)`,
1721
+ padding: "0 80px",
1722
+ }}
1723
+ >
1724
+ <div
1725
+ style={{
1726
+ fontFamily: FONT,
1727
+ fontSize: 90,
1728
+ fontWeight: 800,
1729
+ color: C.ink,
1730
+ letterSpacing: "-0.04em",
1731
+ lineHeight: 1,
1732
+ }}
1733
+ >
1734
+ This isn't{" "}
1735
+ <span style={{ color: C.inkDim, fontWeight: 500 }}>vibe-coding</span>{" "}
1736
+ a chatbot.
1737
+ </div>
1738
+ <div
1739
+ style={{
1740
+ marginTop: 18,
1741
+ fontFamily: MONO,
1742
+ fontSize: 24,
1743
+ fontWeight: 500,
1744
+ color: C.iriViolet,
1745
+ letterSpacing: "0.2em",
1746
+ textTransform: "uppercase",
1747
+ }}
1748
+ >
1749
+ this is an org.
1750
+ </div>
1751
+ </div>
1752
+ </AbsoluteFill>
1753
+ );
1754
+ };
1755
+
1756
+ // ═══════════════════════════════════════════════════════════════
1757
+ // SCENE 10 — FROM PHONE (1454–1561, 3.6s)
1758
+ // "running an entire company from your phone"
1759
+ // ═══════════════════════════════════════════════════════════════
1760
+ const FromPhoneScene: React.FC = () => {
1761
+ const frame = useCurrentFrame();
1762
+ const local = frame - BEAT.fromPhone;
1763
+
1764
+ const phoneScale = spring({
1765
+ frame: local,
1766
+ fps: 30,
1767
+ config: { damping: 13, stiffness: 120 },
1768
+ from: 0.8,
1769
+ to: 1,
1770
+ });
1771
+ const phoneOp = interpolate(local, [0, 22], [0, 1], { extrapolateRight: "clamp" });
1772
+
1773
+ // Tiny org chart inside the phone — 4 mini cards
1774
+ const miniCards = [
1775
+ { label: "CEO", dot: C.iriCyan, at: BEAT.fromPhone + 16 },
1776
+ { label: "Engineers", dot: C.iriViolet, at: BEAT.fromPhone + 22 },
1777
+ { label: "Marketing", dot: C.iriRose, at: BEAT.fromPhone + 28 },
1778
+ { label: "Copy", dot: C.iriGold, at: BEAT.fromPhone + 34 },
1779
+ ];
1780
+
1781
+ return (
1782
+ <AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
1783
+ <FloatingGlyphs />
1784
+
1785
+ {/* Headline above */}
1786
+ <div
1787
+ style={{
1788
+ position: "absolute",
1789
+ top: SAFE_TOP + 40,
1790
+ left: 80,
1791
+ right: 80,
1792
+ textAlign: "center",
1793
+ opacity: interpolate(local, [0, 24], [0, 1], { extrapolateRight: "clamp" }),
1794
+ }}
1795
+ >
1796
+ <StaggeredWords
1797
+ text="Run a whole company"
1798
+ startFrame={BEAT.fromPhone + 4}
1799
+ fontSize={72}
1800
+ fontWeight={600}
1801
+ color={C.inkSoft}
1802
+ align="center"
1803
+ />
1804
+ <div style={{ height: 4 }} />
1805
+ <StaggeredWords
1806
+ text="from your phone."
1807
+ startFrame={BEAT.fromPhone + 22}
1808
+ fontSize={92}
1809
+ fontWeight={800}
1810
+ color={C.ink}
1811
+ align="center"
1812
+ highlight="phone"
1813
+ highlightColor={C.iriViolet}
1814
+ />
1815
+ </div>
1816
+
1817
+ {/* Phone mockup */}
1818
+ <div
1819
+ style={{
1820
+ position: "relative",
1821
+ width: 360,
1822
+ height: 720,
1823
+ opacity: phoneOp,
1824
+ transform: `scale(${phoneScale})`,
1825
+ willChange: "transform, opacity",
1826
+ }}
1827
+ >
1828
+ {/* Phone bezel */}
1829
+ <div
1830
+ style={{
1831
+ ...glassBase,
1832
+ position: "absolute",
1833
+ inset: 0,
1834
+ borderRadius: 56,
1835
+ padding: 16,
1836
+ background: "rgba(14,14,18,0.92)",
1837
+ border: "1.5px solid rgba(255,255,255,0.18)",
1838
+ boxShadow: [
1839
+ "0 30px 60px -16px rgba(120,100,180,0.35)",
1840
+ "inset 0 1.5px 0 rgba(255,255,255,0.4)",
1841
+ ].join(", "),
1842
+ }}
1843
+ >
1844
+ {/* Notch */}
1845
+ <div
1846
+ style={{
1847
+ position: "absolute",
1848
+ top: 16,
1849
+ left: "50%",
1850
+ transform: "translateX(-50%)",
1851
+ width: 110,
1852
+ height: 28,
1853
+ borderRadius: 14,
1854
+ background: "#000",
1855
+ zIndex: 2,
1856
+ }}
1857
+ />
1858
+ {/* Screen */}
1859
+ <div
1860
+ style={{
1861
+ width: "100%",
1862
+ height: "100%",
1863
+ borderRadius: 42,
1864
+ background: `linear-gradient(160deg, ${C.iriCyan}22, ${C.iriViolet}33, ${C.iriRose}22)`,
1865
+ padding: "60px 18px 18px",
1866
+ display: "flex",
1867
+ flexDirection: "column",
1868
+ gap: 12,
1869
+ overflow: "hidden",
1870
+ }}
1871
+ >
1872
+ {/* Mini app header */}
1873
+ <div
1874
+ style={{
1875
+ display: "flex",
1876
+ alignItems: "center",
1877
+ gap: 10,
1878
+ marginBottom: 8,
1879
+ padding: "0 6px",
1880
+ }}
1881
+ >
1882
+ <PaperclipGlyph size={28} startFrame={BEAT.fromPhone + 6} rotate={-12} />
1883
+ <div
1884
+ style={{
1885
+ fontFamily: MONO,
1886
+ fontSize: 14,
1887
+ fontWeight: 600,
1888
+ color: "#fff",
1889
+ letterSpacing: "0.12em",
1890
+ textTransform: "uppercase",
1891
+ }}
1892
+ >
1893
+ paperclip
1894
+ </div>
1895
+ <div
1896
+ style={{
1897
+ marginLeft: "auto",
1898
+ width: 8,
1899
+ height: 8,
1900
+ borderRadius: "50%",
1901
+ background: C.iriCyan,
1902
+ boxShadow: `0 0 10px ${C.iriCyan}`,
1903
+ }}
1904
+ />
1905
+ </div>
1906
+ {/* Mini cards */}
1907
+ {miniCards.map((m, i) => {
1908
+ const cardLocal = frame - m.at;
1909
+ const op = interpolate(cardLocal, [0, 14], [0, 1], { extrapolateRight: "clamp" });
1910
+ const x = interpolate(cardLocal, [0, 22], [40, 0], {
1911
+ extrapolateRight: "clamp",
1912
+ easing: ease.power3Out,
1913
+ });
1914
+ return (
1915
+ <div
1916
+ key={i}
1917
+ style={{
1918
+ background: "rgba(255,255,255,0.12)",
1919
+ backdropFilter: "blur(12px)",
1920
+ WebkitBackdropFilter: "blur(12px)",
1921
+ border: "1px solid rgba(255,255,255,0.18)",
1922
+ borderRadius: 14,
1923
+ padding: "12px 14px",
1924
+ display: "flex",
1925
+ alignItems: "center",
1926
+ gap: 10,
1927
+ opacity: op,
1928
+ transform: `translateX(${x}px)`,
1929
+ willChange: "transform, opacity",
1930
+ }}
1931
+ >
1932
+ <div
1933
+ style={{
1934
+ width: 10,
1935
+ height: 10,
1936
+ borderRadius: "50%",
1937
+ background: m.dot,
1938
+ boxShadow: `0 0 10px ${m.dot}`,
1939
+ }}
1940
+ />
1941
+ <div
1942
+ style={{
1943
+ fontFamily: FONT,
1944
+ fontSize: 16,
1945
+ fontWeight: 600,
1946
+ color: "#fff",
1947
+ letterSpacing: "-0.01em",
1948
+ }}
1949
+ >
1950
+ {m.label}
1951
+ </div>
1952
+ <div
1953
+ style={{
1954
+ marginLeft: "auto",
1955
+ fontFamily: MONO,
1956
+ fontSize: 11,
1957
+ color: "rgba(255,255,255,0.55)",
1958
+ letterSpacing: "0.16em",
1959
+ textTransform: "uppercase",
1960
+ }}
1961
+ >
1962
+ active
1963
+ </div>
1964
+ </div>
1965
+ );
1966
+ })}
1967
+ <div style={{ flex: 1 }} />
1968
+ <div
1969
+ style={{
1970
+ fontFamily: MONO,
1971
+ fontSize: 11,
1972
+ color: "rgba(255,255,255,0.5)",
1973
+ letterSpacing: "0.18em",
1974
+ textTransform: "uppercase",
1975
+ textAlign: "center",
1976
+ padding: "8px 0",
1977
+ }}
1978
+ >
1979
+ org running · 24/7
1980
+ </div>
1981
+ </div>
1982
+ </div>
1983
+ </div>
1984
+ </AbsoluteFill>
1985
+ );
1986
+ };
1987
+
1988
+ // ═══════════════════════════════════════════════════════════════
1989
+ // SCENE 11 — CTA (1561–1650, 3.0s) — "Comment AI"
1990
+ // ═══════════════════════════════════════════════════════════════
1991
+ const CTAScene: React.FC = () => {
1992
+ const frame = useCurrentFrame();
1993
+ const local = frame - BEAT.cta;
1994
+
1995
+ // Eyebrow pill (frame 1561 → local 0)
1996
+ const eyebrowOp = interpolate(local, [0, 12], [0, 1], { extrapolateRight: "clamp" });
1997
+
1998
+ // "Comment" word (frame 1568 → local 7)
1999
+ const commentOp = interpolate(local, [7, 28], [0, 1], { extrapolateRight: "clamp" });
2000
+ const commentY = interpolate(local, [7, 28], [28, 0], {
2001
+ extrapolateRight: "clamp",
2002
+ easing: ease.expoOut,
2003
+ });
2004
+
2005
+ // "AI" word (frame 1580 → local 19)
2006
+ const aiScale = spring({
2007
+ frame: local - 19,
2008
+ fps: 30,
2009
+ config: { damping: 11, stiffness: 120 },
2010
+ from: 0.85,
2011
+ to: 1,
2012
+ });
2013
+ const aiOp = interpolate(local, [19, 35], [0, 1], { extrapolateRight: "clamp" });
2014
+
2015
+ // Caption (frame 1592 → local 31)
2016
+ const captionOp = interpolate(local, [31, 50], [0, 1], { extrapolateRight: "clamp" });
2017
+
2018
+ // Pill (frame 1605 → local 44)
2019
+ const pillOp = interpolate(local, [44, 70], [0, 1], { extrapolateRight: "clamp" });
2020
+ const pillScale = spring({
2021
+ frame: local - 44,
2022
+ fps: 30,
2023
+ config: { damping: 12, stiffness: 100 },
2024
+ from: 0.85,
2025
+ to: 1,
2026
+ });
2027
+
2028
+ // Follow line (frame 1625 → local 64)
2029
+ const followOp = interpolate(local, [64, 86], [0, 1], { extrapolateRight: "clamp" });
2030
+
2031
+ return (
2032
+ <AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
2033
+ {/* Sparkle particle burst (re-fires at frame 1574 / local 13 and 1640 / local 79) */}
2034
+ {frame >= 1574 && frame < 1640 && <ParticleBurst />}
2035
+
2036
+ {/* Eyebrow pill */}
2037
+ <div
2038
+ style={{
2039
+ position: "absolute",
2040
+ top: SAFE_TOP + 40,
2041
+ left: 0,
2042
+ right: 0,
2043
+ display: "flex",
2044
+ justifyContent: "center",
2045
+ opacity: eyebrowOp,
2046
+ }}
2047
+ >
2048
+ <EyebrowPill>GET THE LINK</EyebrowPill>
2049
+ </div>
2050
+
2051
+ {/* Headline stack */}
2052
+ <div style={{ textAlign: "center", marginTop: -100 }}>
2053
+ <div
2054
+ style={{
2055
+ fontFamily: FONT,
2056
+ fontSize: 56,
2057
+ fontWeight: 500,
2058
+ color: C.inkMuted,
2059
+ letterSpacing: "-0.02em",
2060
+ opacity: commentOp,
2061
+ transform: `translateY(${commentY}px)`,
2062
+ }}
2063
+ >
2064
+ Comment
2065
+ </div>
2066
+ <div
2067
+ style={{
2068
+ fontFamily: FONT,
2069
+ fontSize: 240,
2070
+ fontWeight: 800,
2071
+ letterSpacing: "-0.07em",
2072
+ lineHeight: 0.9,
2073
+ opacity: aiOp,
2074
+ transform: `scale(${aiScale})`,
2075
+ background: `linear-gradient(135deg, ${C.iriCyan}, ${C.iriViolet}, ${C.iriRose})`,
2076
+ WebkitBackgroundClip: "text",
2077
+ WebkitTextFillColor: "transparent",
2078
+ backgroundClip: "text",
2079
+ willChange: "transform",
2080
+ }}
2081
+ >
2082
+ "AI"
2083
+ </div>
2084
+ <div
2085
+ style={{
2086
+ marginTop: -8,
2087
+ fontFamily: FONT,
2088
+ fontSize: 44,
2089
+ fontWeight: 500,
2090
+ color: C.inkMuted,
2091
+ letterSpacing: "-0.02em",
2092
+ opacity: captionOp,
2093
+ }}
2094
+ >
2095
+ and I'll DM the GitHub repo.
2096
+ </div>
2097
+ </div>
2098
+
2099
+ {/* Iridescent pill with handle */}
2100
+ <div
2101
+ style={{
2102
+ position: "relative",
2103
+ marginTop: 36,
2104
+ opacity: pillOp,
2105
+ transform: `scale(${pillScale})`,
2106
+ willChange: "transform, opacity",
2107
+ }}
2108
+ >
2109
+ {/* Conic ring around pill */}
2110
+ <div
2111
+ style={{
2112
+ position: "absolute",
2113
+ inset: -3,
2114
+ borderRadius: 9999,
2115
+ background: `conic-gradient(from ${frame * 1.2}deg, ${C.iriCyan}, ${C.iriViolet}, ${C.iriRose}, ${C.iriGold}, ${C.iriCyan})`,
2116
+ filter: "blur(2px)",
2117
+ opacity: 0.85,
2118
+ }}
2119
+ />
2120
+ <div
2121
+ style={{
2122
+ ...glassBase,
2123
+ background: C.glassFillStrong,
2124
+ borderRadius: 9999,
2125
+ padding: "22px 44px",
2126
+ display: "inline-flex",
2127
+ alignItems: "center",
2128
+ gap: 16,
2129
+ position: "relative",
2130
+ }}
2131
+ >
2132
+ {/* Instagram glyph */}
2133
+ <svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke={C.ink} strokeWidth="1.6">
2134
+ <rect x="3" y="3" width="18" height="18" rx="5" />
2135
+ <circle cx="12" cy="12" r="4" />
2136
+ <circle cx="17.5" cy="6.5" r="1" fill={C.ink} />
2137
+ </svg>
2138
+ <div
2139
+ style={{
2140
+ fontFamily: FONT,
2141
+ fontSize: 36,
2142
+ fontWeight: 700,
2143
+ color: C.ink,
2144
+ letterSpacing: "-0.02em",
2145
+ }}
2146
+ >
2147
+ @abhishek.devini
2148
+ </div>
2149
+ </div>
2150
+ </div>
2151
+
2152
+ {/* Follow line */}
2153
+ <div
2154
+ style={{
2155
+ marginTop: 32,
2156
+ opacity: followOp,
2157
+ fontFamily: MONO,
2158
+ fontSize: 22,
2159
+ color: C.inkMuted,
2160
+ letterSpacing: "0.22em",
2161
+ textTransform: "uppercase",
2162
+ }}
2163
+ >
2164
+ follow for more →
2165
+ </div>
2166
+ </AbsoluteFill>
2167
+ );
2168
+ };
2169
+
2170
+ // ═══════════════════════════════════════════════════════════════
2171
+ // MAIN — orchestrate scenes with audio at root
2172
+ // ═══════════════════════════════════════════════════════════════
2173
+ export const PaperclipReel: React.FC = () => {
2174
+ const { width, height } = useVideoConfig();
2175
+ const frame = useCurrentFrame();
2176
+
2177
+ return (
2178
+ <AbsoluteFill
2179
+ style={{
2180
+ background: `radial-gradient(ellipse at 30% 20%, ${C.bgWarm} 0%, ${C.bg} 50%, ${C.bgCool} 100%)`,
2181
+ width,
2182
+ height,
2183
+ overflow: "hidden",
2184
+ }}
2185
+ >
2186
+ {/* Voiceover — single Audio at root, no Sequence wrap */}
2187
+ {/* REFERENCE-STRIP: <Audio> tag removed — bring your own voiceover */}
2188
+
2189
+ {/* Perpetual layers */}
2190
+ <CausticBlobs />
2191
+ <HairlineGrid opacity={0.04} />
2192
+
2193
+ {/* Film-grain noise (fixed pseudo-element pattern) */}
2194
+ <AbsoluteFill
2195
+ style={{
2196
+ backgroundImage:
2197
+ "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E\")",
2198
+ opacity: 0.06,
2199
+ pointerEvents: "none",
2200
+ mixBlendMode: "multiply",
2201
+ }}
2202
+ />
2203
+
2204
+ {/* Scenes — conditional render so useCurrentFrame() stays GLOBAL inside each scene */}
2205
+ {frame >= BEAT.hook && frame < BEAT.replaced && <HookScene />}
2206
+ {frame >= BEAT.replaced && frame < BEAT.notOpenAI && <ReplacedScene />}
2207
+ {frame >= BEAT.notOpenAI && frame < BEAT.reveal && <NotOpenAIScene />}
2208
+ {frame >= BEAT.reveal && frame < BEAT.license && <RevealScene />}
2209
+ {frame >= BEAT.license && frame < BEAT.companies && <LicenseScene />}
2210
+ {frame >= BEAT.companies && frame < BEAT.notFramework && <CompaniesScene />}
2211
+ {frame >= BEAT.notFramework && frame < BEAT.demoStart && <NotFrameworkScene />}
2212
+ {frame >= BEAT.demoStart && frame < BEAT.demoEnd && <DemoScene />}
2213
+ {frame >= BEAT.notVibe && frame < BEAT.fromPhone && <NotVibeScene />}
2214
+ {frame >= BEAT.fromPhone && frame < BEAT.cta && <FromPhoneScene />}
2215
+ {frame >= BEAT.cta && frame < BEAT.end && <CTAScene />}
2216
+ </AbsoluteFill>
2217
+ );
2218
+ };