@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,1898 @@
1
+ /**
2
+ * REFERENCE — JustDropReel (Family: paper)
3
+ *
4
+ * Canonical example of the paper 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/JustDropReel.tsx
15
+ * Bundled at: 2026-05-08T18:50:39.507Z
16
+ */
17
+ import {
18
+ useCurrentFrame,
19
+ useVideoConfig,
20
+ interpolate,
21
+ spring,
22
+ Easing,
23
+ AbsoluteFill,
24
+ Img,
25
+ OffthreadVideo,
26
+ Sequence,
27
+ staticFile,
28
+ } from "remotion";
29
+ import { Audio } from "@remotion/media";
30
+ import { ds } from "./designSystem";
31
+
32
+ // ═══════════════════════════════════════════════════════════════
33
+ // CONSTANTS
34
+ // ═══════════════════════════════════════════════════════════════
35
+
36
+ const FONT = ds.font.sans;
37
+ const MONO = ds.font.mono;
38
+ const G = ds.card3d.green;
39
+
40
+ // Light-mode text palette (cream paper background) — matches OpusReel
41
+ const T = {
42
+ heading: "#1a1a1a",
43
+ body: "#2d2d2d",
44
+ muted: "#6b6b6b",
45
+ accent: ds.color.claude, // #D4663A terracotta
46
+ accentDark: "#1e3a27",
47
+ danger: "#c0392b",
48
+ success: "#1e7a45",
49
+ } as const;
50
+
51
+ // Scene timeline: aligned to 58.52s voiceover
52
+ // Timing follows the 7 script segments with natural pacing
53
+ const SEGS = [
54
+ { s: 0, e: 7.5, text: "Hook" }, // 1: "using Claude Code... missing the point"
55
+ { s: 7, e: 18.5, text: "Powers" }, // 2: "superpowers for your AI... install once"
56
+ { s: 18, e: 29.5, text: "Reveal" }, // 3: "JustDrop... 1000+ skills... all free"
57
+ { s: 29, e: 37.5, text: "Examples" }, // 4: "UI, testing, SEO, writing"
58
+ { s: 37, e: 46.5, text: "Platforms" }, // 5: "works everywhere"
59
+ { s: 46, e: 53.5, text: "Setup" }, // 6: "pick, copy, paste, done"
60
+ { s: 53, e: 58.5, text: "CTA" }, // 7: "drop Claude in comments"
61
+ ] as const;
62
+
63
+ // ═══════════════════════════════════════════════════════════════
64
+ // UTILITIES
65
+ // ═══════════════════════════════════════════════════════════════
66
+
67
+ const so = (
68
+ frame: number,
69
+ fps: number,
70
+ startS: number,
71
+ endS: number,
72
+ fadeIn = 0.5,
73
+ fadeOut = 0.5,
74
+ ) =>
75
+ interpolate(
76
+ frame,
77
+ [startS * fps, (startS + fadeIn) * fps, (endS - fadeOut) * fps, endS * fps],
78
+ [0, 1, 1, 0],
79
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" },
80
+ );
81
+
82
+ const lf = (frame: number, fps: number, startSec: number) =>
83
+ Math.max(0, frame - startSec * fps);
84
+
85
+ interface SP {
86
+ frame: number;
87
+ fps: number;
88
+ }
89
+
90
+ // ═══════════════════════════════════════════════════════════════
91
+ // BACKGROUND — warm cream paper with subtle grid (matches OpusReel)
92
+ // ═══════════════════════════════════════════════════════════════
93
+
94
+ const Background: React.FC<{ frame: number }> = ({ frame }) => {
95
+ const paperBase = "#e8e4de";
96
+ const paperLight = "#edeae4";
97
+ const gridLine = "rgba(180,175,165,0.25)";
98
+ const vignetteOp = 0.12 + Math.sin(frame * 0.006) * 0.03;
99
+
100
+ return (
101
+ <AbsoluteFill>
102
+ <div
103
+ style={{
104
+ width: "100%",
105
+ height: "100%",
106
+ background: `radial-gradient(ellipse at 50% 40%, ${paperLight} 0%, ${paperBase} 55%, #d9d4cc 100%)`,
107
+ }}
108
+ />
109
+ <div
110
+ style={{
111
+ position: "absolute",
112
+ inset: 0,
113
+ backgroundImage: `
114
+ linear-gradient(${gridLine} 1px, transparent 1px),
115
+ linear-gradient(90deg, ${gridLine} 1px, transparent 1px)
116
+ `,
117
+ backgroundSize: "48px 48px",
118
+ }}
119
+ />
120
+ <div
121
+ style={{
122
+ position: "absolute",
123
+ inset: 0,
124
+ background: `radial-gradient(ellipse at 50% 45%, rgba(255,255,255,0.3) 0%, transparent 60%)`,
125
+ }}
126
+ />
127
+ <div
128
+ style={{
129
+ position: "absolute",
130
+ inset: 0,
131
+ background: `radial-gradient(ellipse at 50% 50%, transparent 50%, rgba(0,0,0,${vignetteOp}) 100%)`,
132
+ }}
133
+ />
134
+ </AbsoluteFill>
135
+ );
136
+ };
137
+
138
+ // ═══════════════════════════════════════════════════════════════
139
+ // 3D FLOATING CARD — reusable component from OpusReel
140
+ // ═══════════════════════════════════════════════════════════════
141
+
142
+ const FloatingCard: React.FC<{
143
+ children: React.ReactNode;
144
+ width?: number | string;
145
+ rotateX?: number;
146
+ rotateY?: number;
147
+ scale?: number;
148
+ opacity?: number;
149
+ style?: React.CSSProperties;
150
+ }> = ({
151
+ children,
152
+ width = "85%",
153
+ rotateX = 2,
154
+ rotateY = -1,
155
+ scale = 1,
156
+ opacity = 1,
157
+ style,
158
+ }) => (
159
+ <div
160
+ style={{
161
+ width,
162
+ maxWidth: 900,
163
+ perspective: 1200,
164
+ opacity,
165
+ ...style,
166
+ }}
167
+ >
168
+ <div
169
+ style={{
170
+ ...ds.card3d.cardStyle,
171
+ transform: `rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale(${scale})`,
172
+ transformStyle: "preserve-3d",
173
+ padding: "32px 36px",
174
+ position: "relative",
175
+ overflow: "hidden",
176
+ }}
177
+ >
178
+ <div
179
+ style={{
180
+ position: "absolute",
181
+ top: 0,
182
+ left: 20,
183
+ right: 20,
184
+ height: 1,
185
+ background:
186
+ "linear-gradient(90deg, transparent, rgba(255,255,255,0.12), transparent)",
187
+ }}
188
+ />
189
+ {children}
190
+ </div>
191
+ </div>
192
+ );
193
+
194
+ // ═══════════════════════════════════════════════════════════════
195
+ // 3D SHAPES — pure CSS primitives (cone, cube, sphere)
196
+ // ═══════════════════════════════════════════════════════════════
197
+
198
+ /** 3D rotating Claude app icon — uses the official asset from /public */
199
+ const JustDropShape: React.FC<{
200
+ size: number;
201
+ rotation?: number;
202
+ glow?: number;
203
+ }> = ({ size, rotation = 0, glow = 0.4 }) => {
204
+ return (
205
+ <div
206
+ style={{
207
+ width: size,
208
+ height: size,
209
+ position: "relative",
210
+ perspective: 800,
211
+ }}
212
+ >
213
+ <div
214
+ style={{
215
+ position: "absolute",
216
+ inset: 0,
217
+ transformStyle: "preserve-3d",
218
+ transform: `rotateY(${rotation}deg) rotateX(-8deg)`,
219
+ }}
220
+ >
221
+ {/* Soft background glow */}
222
+ <div
223
+ style={{
224
+ position: "absolute",
225
+ inset: -size * 0.2,
226
+ borderRadius: "50%",
227
+ background: `radial-gradient(circle, rgba(212,102,58,${glow}) 0%, transparent 60%)`,
228
+ filter: "blur(24px)",
229
+ }}
230
+ />
231
+ {/* Real Claude icon asset */}
232
+ <Img
233
+ src={staticFile("captures/your-asset.png" /* REFERENCE-STRIP */)}
234
+ style={{
235
+ width: size,
236
+ height: size,
237
+ display: "block",
238
+ filter: "drop-shadow(0 18px 28px rgba(150,60,30,0.4))",
239
+ }}
240
+ />
241
+ </div>
242
+ </div>
243
+ );
244
+ };
245
+
246
+ /** 3D isometric cube — used for skills visualization */
247
+ const IsoCube: React.FC<{
248
+ size: number;
249
+ color?: string;
250
+ delay?: number;
251
+ frame: number;
252
+ fps: number;
253
+ }> = ({ size, color = G.surface, delay = 0, frame, fps }) => {
254
+ const s = spring({ frame, fps, delay, config: { damping: 14, stiffness: 90 } });
255
+ const op = interpolate(s, [0, 0.3], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
256
+ const sc = interpolate(s, [0, 1], [0.3, 1]);
257
+ const w = size;
258
+ const h = size;
259
+ return (
260
+ <div
261
+ style={{
262
+ width: w,
263
+ height: h * 1.15,
264
+ position: "relative",
265
+ opacity: op,
266
+ transform: `scale(${sc})`,
267
+ }}
268
+ >
269
+ {/* Top face */}
270
+ <div
271
+ style={{
272
+ position: "absolute",
273
+ top: 0,
274
+ left: w * 0.25,
275
+ width: w * 0.5,
276
+ height: h * 0.3,
277
+ background: `linear-gradient(135deg, ${color}, ${G.bgLight})`,
278
+ transform: "rotateX(60deg) rotateZ(-45deg)",
279
+ transformOrigin: "center bottom",
280
+ borderTop: "1px solid rgba(255,255,255,0.15)",
281
+ }}
282
+ />
283
+ {/* Left face */}
284
+ <div
285
+ style={{
286
+ position: "absolute",
287
+ top: h * 0.28,
288
+ left: 0,
289
+ width: w * 0.5,
290
+ height: h * 0.7,
291
+ background: `linear-gradient(180deg, ${G.bg}, #0d1812)`,
292
+ borderRight: "1px solid rgba(255,255,255,0.08)",
293
+ clipPath: "polygon(0 15%, 100% 0, 100% 85%, 0 100%)",
294
+ }}
295
+ />
296
+ {/* Right face */}
297
+ <div
298
+ style={{
299
+ position: "absolute",
300
+ top: h * 0.28,
301
+ right: 0,
302
+ width: w * 0.5,
303
+ height: h * 0.7,
304
+ background: `linear-gradient(180deg, ${G.bgLight}, ${G.bg})`,
305
+ borderLeft: "1px solid rgba(255,255,255,0.12)",
306
+ clipPath: "polygon(0 0, 100% 15%, 100% 100%, 0 85%)",
307
+ }}
308
+ />
309
+ </div>
310
+ );
311
+ };
312
+
313
+ /** 3D rotating ring — orbital decoration */
314
+ const OrbitRing: React.FC<{
315
+ size: number;
316
+ rotation: number;
317
+ tilt: number;
318
+ color?: string;
319
+ }> = ({ size, rotation, tilt, color = "rgba(212,102,58,0.4)" }) => (
320
+ <div
321
+ style={{
322
+ width: size,
323
+ height: size,
324
+ borderRadius: "50%",
325
+ border: `1.5px solid ${color}`,
326
+ transform: `rotateX(${tilt}deg) rotateZ(${rotation}deg)`,
327
+ transformStyle: "preserve-3d",
328
+ }}
329
+ />
330
+ );
331
+
332
+ // ═══════════════════════════════════════════════════════════════
333
+ // ICON PRIMITIVES — clean SVG (no emojis per design guidelines)
334
+ // ═══════════════════════════════════════════════════════════════
335
+
336
+ const Icon = {
337
+ Brush: ({ color = "#fff", size = 28 }) => (
338
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
339
+ <path d="M3 21c0-1.5 1-3 3-3 2 0 3 1.5 3 3H3zm18-18L9 15l-1 5 5-1L25 7" stroke={color} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" transform="translate(-2 0)" />
340
+ </svg>
341
+ ),
342
+ Beaker: ({ color = "#fff", size = 28 }) => (
343
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
344
+ <path d="M9 3h6v5l5 11a2 2 0 0 1-2 3H6a2 2 0 0 1-2-3l5-11V3z" stroke={color} strokeWidth="1.8" strokeLinejoin="round" />
345
+ <path d="M7 14h10" stroke={color} strokeWidth="1.8" />
346
+ </svg>
347
+ ),
348
+ Chart: ({ color = "#fff", size = 28 }) => (
349
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
350
+ <path d="M3 3v18h18" stroke={color} strokeWidth="1.8" strokeLinecap="round" />
351
+ <path d="M7 14l4-4 3 3 5-6" stroke={color} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
352
+ </svg>
353
+ ),
354
+ Pen: ({ color = "#fff", size = 28 }) => (
355
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
356
+ <path d="M4 20l4-1 11-11-3-3L5 16l-1 4z" stroke={color} strokeWidth="1.8" strokeLinejoin="round" />
357
+ <path d="M14 5l3 3" stroke={color} strokeWidth="1.8" />
358
+ </svg>
359
+ ),
360
+ Terminal: ({ color = "#fff", size = 28 }) => (
361
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
362
+ <rect x="2.5" y="4" width="19" height="16" rx="2" stroke={color} strokeWidth="1.8" />
363
+ <path d="M7 9l3 3-3 3M13 15h4" stroke={color} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
364
+ </svg>
365
+ ),
366
+ Sparkle: ({ color = "#fff", size = 28 }) => (
367
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
368
+ <path d="M12 3l2 6 6 2-6 2-2 6-2-6-6-2 6-2 2-6z" fill={color} />
369
+ </svg>
370
+ ),
371
+ Check: ({ color = "#fff", size = 20 }) => (
372
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
373
+ <path d="M5 12l5 5L20 7" stroke={color} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
374
+ </svg>
375
+ ),
376
+ ArrowDown: ({ color = "#fff", size = 32 }) => (
377
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
378
+ <path d="M12 5v14M6 13l6 6 6-6" stroke={color} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" />
379
+ </svg>
380
+ ),
381
+ Chat: ({ color = "#fff", size = 28 }) => (
382
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
383
+ <path d="M3 6a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-8l-5 4v-4H5a2 2 0 0 1-2-2V6z" stroke={color} strokeWidth="1.8" strokeLinejoin="round" />
384
+ </svg>
385
+ ),
386
+ };
387
+
388
+ // ═══════════════════════════════════════════════════════════════
389
+ // SCENE 1: HOOK — "If you're not messing with skills yet..."
390
+ // ═══════════════════════════════════════════════════════════════
391
+
392
+ const HookScene: React.FC<SP> = ({ frame, fps }) => {
393
+ const op = so(frame, fps, SEGS[0].s, SEGS[0].e);
394
+ if (op === 0) return null;
395
+ const f = lf(frame, fps, SEGS[0].s);
396
+
397
+ const badgeSpring = spring({
398
+ frame: f,
399
+ fps,
400
+ delay: Math.round(0.3 * fps),
401
+ config: ds.spring.bouncy,
402
+ });
403
+
404
+ const titleSpring = spring({
405
+ frame: f,
406
+ fps,
407
+ delay: Math.round(0.8 * fps),
408
+ config: ds.spring.gentle,
409
+ });
410
+ const titleScale = interpolate(titleSpring, [0, 1], [0.85, 1]);
411
+
412
+ const subOp = interpolate(f, [2.8 * fps, 3.5 * fps], [0, 1], {
413
+ extrapolateLeft: "clamp",
414
+ extrapolateRight: "clamp",
415
+ });
416
+ const subY = interpolate(f, [2.8 * fps, 3.5 * fps], [30, 0], {
417
+ extrapolateLeft: "clamp",
418
+ extrapolateRight: "clamp",
419
+ easing: Easing.out(Easing.quad),
420
+ });
421
+
422
+ // Subtle float for the shape
423
+ const floatY = Math.sin(f / fps * 1.4) * 6;
424
+ const shapeRot = interpolate(f, [0, 7.5 * fps], [-15, 15]);
425
+
426
+ return (
427
+ <AbsoluteFill style={{ opacity: op }}>
428
+ {/* Top pill badge */}
429
+ <div
430
+ style={{
431
+ position: "absolute",
432
+ top: "17%",
433
+ left: "50%",
434
+ transform: `translate(-50%, 0) scale(${interpolate(badgeSpring, [0, 1], [0.5, 1])})`,
435
+ opacity: interpolate(badgeSpring, [0, 0.3], [0, 1], {
436
+ extrapolateLeft: "clamp",
437
+ extrapolateRight: "clamp",
438
+ }),
439
+ ...ds.card3d.pillStyle,
440
+ display: "flex",
441
+ alignItems: "center",
442
+ gap: 10,
443
+ }}
444
+ >
445
+ <div
446
+ style={{
447
+ width: 8,
448
+ height: 8,
449
+ borderRadius: "50%",
450
+ background: ds.color.claude,
451
+ boxShadow: `0 0 10px ${ds.color.claude}`,
452
+ }}
453
+ />
454
+ <span
455
+ style={{
456
+ fontFamily: MONO,
457
+ fontSize: 15,
458
+ fontWeight: ds.text.medium,
459
+ color: G.text,
460
+ letterSpacing: 1.2,
461
+ }}
462
+ >
463
+ CLAUDE CODE TIP
464
+ </span>
465
+ </div>
466
+
467
+ {/* Big droplet shape above title */}
468
+ <div
469
+ style={{
470
+ position: "absolute",
471
+ top: "26%",
472
+ left: "50%",
473
+ transform: `translate(-50%, ${floatY}px)`,
474
+ opacity: interpolate(badgeSpring, [0.2, 1], [0, 1], {
475
+ extrapolateLeft: "clamp",
476
+ extrapolateRight: "clamp",
477
+ }),
478
+ }}
479
+ >
480
+ <JustDropShape size={180} rotation={shapeRot} glow={0.3} />
481
+ </div>
482
+
483
+ {/* Title */}
484
+ <div
485
+ style={{
486
+ position: "absolute",
487
+ top: "48%",
488
+ width: "100%",
489
+ textAlign: "center",
490
+ padding: "0 48px",
491
+ transform: `scale(${titleScale})`,
492
+ opacity: interpolate(titleSpring, [0, 0.2], [0, 1], {
493
+ extrapolateLeft: "clamp",
494
+ extrapolateRight: "clamp",
495
+ }),
496
+ }}
497
+ >
498
+ <div
499
+ style={{
500
+ fontFamily: FONT,
501
+ fontSize: 68,
502
+ fontWeight: ds.text.bold,
503
+ color: T.heading,
504
+ lineHeight: ds.text.lineTight,
505
+ letterSpacing: ds.text.tighter,
506
+ }}
507
+ >
508
+ Not using{" "}
509
+ <span style={{ color: ds.color.claude }}>Skills</span>?
510
+ </div>
511
+ </div>
512
+
513
+ {/* Subtitle */}
514
+ <div
515
+ style={{
516
+ position: "absolute",
517
+ top: "68%",
518
+ width: "100%",
519
+ textAlign: "center",
520
+ padding: "0 60px",
521
+ opacity: subOp,
522
+ transform: `translateY(${subY}px)`,
523
+ }}
524
+ >
525
+ <div
526
+ style={{
527
+ fontFamily: FONT,
528
+ fontSize: 30,
529
+ fontWeight: ds.text.regular,
530
+ color: T.muted,
531
+ lineHeight: ds.text.lineNormal,
532
+ }}
533
+ >
534
+ You're kinda missing
535
+ <br />
536
+ <span style={{ color: T.heading, fontWeight: ds.text.semibold }}>
537
+ the whole point.
538
+ </span>
539
+ </div>
540
+ </div>
541
+ </AbsoluteFill>
542
+ );
543
+ };
544
+
545
+ // ═══════════════════════════════════════════════════════════════
546
+ // SCENE 2: POWERS — "Skills = superpowers for your AI"
547
+ // ═══════════════════════════════════════════════════════════════
548
+
549
+ const PowersScene: React.FC<SP> = ({ frame, fps }) => {
550
+ const op = so(frame, fps, SEGS[1].s, SEGS[1].e);
551
+ if (op === 0) return null;
552
+ const f = lf(frame, fps, SEGS[1].s);
553
+
554
+ const headOp = interpolate(f, [0.3 * fps, 0.9 * fps], [0, 1], {
555
+ extrapolateLeft: "clamp",
556
+ extrapolateRight: "clamp",
557
+ });
558
+
559
+ const bigSpring = spring({
560
+ frame: f,
561
+ fps,
562
+ delay: Math.round(0.8 * fps),
563
+ config: { damping: 10, stiffness: 90 },
564
+ });
565
+
566
+ const cardSpring = spring({
567
+ frame: f,
568
+ fps,
569
+ delay: Math.round(2.2 * fps),
570
+ config: ds.spring.glass,
571
+ });
572
+
573
+ // Prompt card animation — frame that gets "replaced" by install
574
+ const typedChars = Math.floor(
575
+ interpolate(f, [4.2 * fps, 6 * fps], [0, 42], {
576
+ extrapolateLeft: "clamp",
577
+ extrapolateRight: "clamp",
578
+ }),
579
+ );
580
+ const longPrompt = "Act as senior dev, review my code, check ";
581
+ const typedText = longPrompt.slice(0, typedChars);
582
+
583
+ // "strikethrough" long prompt, replaced by short skill
584
+ const strikeOp = interpolate(f, [6.3 * fps, 7 * fps], [0, 1], {
585
+ extrapolateLeft: "clamp",
586
+ extrapolateRight: "clamp",
587
+ });
588
+ const skillOp = interpolate(f, [7 * fps, 7.7 * fps], [0, 1], {
589
+ extrapolateLeft: "clamp",
590
+ extrapolateRight: "clamp",
591
+ });
592
+
593
+ // Forever pulse
594
+ const foreverPulse = 1 + Math.sin((f / fps) * 2.4) * 0.025;
595
+
596
+ return (
597
+ <AbsoluteFill style={{ opacity: op }}>
598
+ {/* Label */}
599
+ <div
600
+ style={{
601
+ position: "absolute",
602
+ top: "16%",
603
+ width: "100%",
604
+ textAlign: "center",
605
+ opacity: headOp,
606
+ }}
607
+ >
608
+ <div
609
+ style={{
610
+ fontFamily: FONT,
611
+ fontSize: 22,
612
+ fontWeight: ds.text.medium,
613
+ color: T.muted,
614
+ letterSpacing: ds.text.wider,
615
+ textTransform: "uppercase",
616
+ }}
617
+ >
618
+ Think of skills as
619
+ </div>
620
+ </div>
621
+
622
+ {/* Big title */}
623
+ <div
624
+ style={{
625
+ position: "absolute",
626
+ top: "23%",
627
+ width: "100%",
628
+ textAlign: "center",
629
+ padding: "0 48px",
630
+ transform: `scale(${interpolate(bigSpring, [0, 1], [0.6, 1])})`,
631
+ opacity: interpolate(bigSpring, [0, 0.2], [0, 1], {
632
+ extrapolateLeft: "clamp",
633
+ extrapolateRight: "clamp",
634
+ }),
635
+ }}
636
+ >
637
+ <div
638
+ style={{
639
+ fontFamily: FONT,
640
+ fontSize: 76,
641
+ fontWeight: ds.text.bold,
642
+ color: T.heading,
643
+ lineHeight: 1.05,
644
+ letterSpacing: ds.text.tighter,
645
+ }}
646
+ >
647
+ <span style={{ color: ds.color.claude }}>Superpowers</span>
648
+ <br />
649
+ for your AI
650
+ </div>
651
+ </div>
652
+
653
+ {/* Prompt comparison card — "before / after" */}
654
+ <div
655
+ style={{
656
+ position: "absolute",
657
+ top: "48%",
658
+ left: "50%",
659
+ transform: `translate(-50%, ${interpolate(cardSpring, [0, 1], [60, 0])}px)`,
660
+ opacity: interpolate(cardSpring, [0, 0.3], [0, 1], {
661
+ extrapolateLeft: "clamp",
662
+ extrapolateRight: "clamp",
663
+ }),
664
+ width: "86%",
665
+ }}
666
+ >
667
+ <FloatingCard rotateX={3} rotateY={-1}>
668
+ {/* Before row — long prompt being typed */}
669
+ <div
670
+ style={{
671
+ display: "flex",
672
+ alignItems: "center",
673
+ gap: 14,
674
+ padding: "14px 0",
675
+ opacity: strikeOp > 0 ? 1 - strikeOp * 0.45 : 1,
676
+ }}
677
+ >
678
+ <div
679
+ style={{
680
+ fontFamily: MONO,
681
+ fontSize: 11,
682
+ fontWeight: ds.text.semibold,
683
+ color: "#9B6B4F",
684
+ letterSpacing: 1.2,
685
+ width: 60,
686
+ }}
687
+ >
688
+ BEFORE
689
+ </div>
690
+ <div
691
+ style={{
692
+ flex: 1,
693
+ fontFamily: MONO,
694
+ fontSize: 18,
695
+ color: G.text,
696
+ padding: "10px 14px",
697
+ background: "rgba(0,0,0,0.25)",
698
+ borderRadius: ds.radius.md,
699
+ border: "1px solid rgba(255,255,255,0.05)",
700
+ minHeight: 26,
701
+ position: "relative",
702
+ }}
703
+ >
704
+ <span
705
+ style={{
706
+ position: "relative",
707
+ display: "inline-block",
708
+ }}
709
+ >
710
+ {typedText}
711
+ {typedChars < 42 && typedChars > 0 && (
712
+ <span style={{ opacity: Math.sin(f * 0.4) > 0 ? 1 : 0 }}>|</span>
713
+ )}
714
+ {/* Animated strike line — avoids React textDecoration conflict warning */}
715
+ {strikeOp > 0 && (
716
+ <span
717
+ style={{
718
+ position: "absolute",
719
+ top: "50%",
720
+ left: 0,
721
+ width: `${Math.min(strikeOp * 100, 100)}%`,
722
+ height: 2,
723
+ background: "rgba(255,100,100,0.75)",
724
+ transform: "translateY(-50%)",
725
+ borderRadius: 1,
726
+ }}
727
+ />
728
+ )}
729
+ </span>
730
+ </div>
731
+ </div>
732
+
733
+ {/* Divider */}
734
+ <div
735
+ style={{
736
+ height: 1,
737
+ background: "rgba(255,255,255,0.06)",
738
+ margin: "4px 0",
739
+ }}
740
+ />
741
+
742
+ {/* After row — install once */}
743
+ <div
744
+ style={{
745
+ display: "flex",
746
+ alignItems: "center",
747
+ gap: 14,
748
+ padding: "14px 0",
749
+ opacity: skillOp,
750
+ transform: `translateX(${interpolate(skillOp, [0, 1], [-20, 0])}px)`,
751
+ }}
752
+ >
753
+ <div
754
+ style={{
755
+ fontFamily: MONO,
756
+ fontSize: 11,
757
+ fontWeight: ds.text.semibold,
758
+ color: ds.color.claude,
759
+ letterSpacing: 1.2,
760
+ width: 60,
761
+ }}
762
+ >
763
+ AFTER
764
+ </div>
765
+ <div
766
+ style={{
767
+ flex: 1,
768
+ fontFamily: MONO,
769
+ fontSize: 18,
770
+ color: G.text,
771
+ padding: "10px 14px",
772
+ background: "rgba(212,102,58,0.12)",
773
+ borderRadius: ds.radius.md,
774
+ border: "1px solid rgba(212,102,58,0.25)",
775
+ display: "flex",
776
+ alignItems: "center",
777
+ gap: 10,
778
+ }}
779
+ >
780
+ <span style={{ color: ds.color.claude, fontWeight: ds.text.bold }}>/</span>
781
+ <span>code-review</span>
782
+ <span style={{ marginLeft: "auto", fontSize: 13, color: G.textMuted }}>
783
+ installed ·
784
+ </span>
785
+ <Icon.Sparkle color={ds.color.claude} size={16} />
786
+ </div>
787
+ </div>
788
+ </FloatingCard>
789
+ </div>
790
+
791
+ {/* "Forever" tagline */}
792
+ <div
793
+ style={{
794
+ position: "absolute",
795
+ bottom: "24%",
796
+ width: "100%",
797
+ textAlign: "center",
798
+ opacity: interpolate(f, [8.5 * fps, 9.3 * fps], [0, 1], {
799
+ extrapolateLeft: "clamp",
800
+ extrapolateRight: "clamp",
801
+ }),
802
+ transform: `scale(${foreverPulse})`,
803
+ }}
804
+ >
805
+ <div
806
+ style={{
807
+ fontFamily: FONT,
808
+ fontSize: 34,
809
+ fontWeight: ds.text.semibold,
810
+ color: T.heading,
811
+ }}
812
+ >
813
+ Install once.{" "}
814
+ <span style={{ color: ds.color.claude, fontWeight: ds.text.bold }}>
815
+ Works forever.
816
+ </span>
817
+ </div>
818
+ </div>
819
+ </AbsoluteFill>
820
+ );
821
+ };
822
+
823
+ // ═══════════════════════════════════════════════════════════════
824
+ // SCENE 3: REVEAL — "1000+ skills · free" with screen recording
825
+ // ═══════════════════════════════════════════════════════════════
826
+
827
+ const RevealScene: React.FC<SP> = ({ frame, fps }) => {
828
+ const op = so(frame, fps, SEGS[2].s, SEGS[2].e);
829
+ if (op === 0) return null;
830
+ const f = lf(frame, fps, SEGS[2].s);
831
+
832
+ // Logo entrance
833
+ const logoSpring = spring({
834
+ frame: f,
835
+ fps,
836
+ delay: Math.round(0.3 * fps),
837
+ config: { damping: 10, stiffness: 85 },
838
+ });
839
+ const logoRot = interpolate(f, [0, 11.5 * fps], [-25, 25]);
840
+ const logoFloat = Math.sin(f / fps * 1.8) * 8;
841
+
842
+ // Screen recording entrance
843
+ const videoSpring = spring({
844
+ frame: f,
845
+ fps,
846
+ delay: Math.round(1 * fps),
847
+ config: { damping: 14, stiffness: 95 },
848
+ });
849
+
850
+ // Counter 0 → 1000
851
+ const count = Math.round(
852
+ interpolate(f, [3.5 * fps, 6.5 * fps], [0, 1000], {
853
+ extrapolateLeft: "clamp",
854
+ extrapolateRight: "clamp",
855
+ easing: Easing.out(Easing.cubic),
856
+ }),
857
+ );
858
+ const countOp = interpolate(f, [3.5 * fps, 4 * fps], [0, 1], {
859
+ extrapolateLeft: "clamp",
860
+ extrapolateRight: "clamp",
861
+ });
862
+ const countScale = interpolate(f, [6.3 * fps, 6.7 * fps, 7 * fps], [1, 1.08, 1], {
863
+ extrapolateLeft: "clamp",
864
+ extrapolateRight: "clamp",
865
+ });
866
+
867
+ // Free badge pop
868
+ const freeSpring = spring({
869
+ frame: f,
870
+ fps,
871
+ delay: Math.round(7.8 * fps),
872
+ config: { damping: 8, stiffness: 110 },
873
+ });
874
+
875
+ // Orbiting rings
876
+ const ringRot = (f / fps) * 40;
877
+
878
+ return (
879
+ <AbsoluteFill style={{ opacity: op }}>
880
+ {/* Orbiting rings behind logo */}
881
+ <div
882
+ style={{
883
+ position: "absolute",
884
+ top: "27%",
885
+ left: "50%",
886
+ transform: `translate(-50%, 0)`,
887
+ opacity: interpolate(logoSpring, [0, 1], [0, 0.6]),
888
+ }}
889
+ >
890
+ <div style={{ position: "absolute", top: -90, left: -180 }}>
891
+ <OrbitRing size={380} rotation={ringRot} tilt={70} />
892
+ </div>
893
+ <div style={{ position: "absolute", top: -60, left: -150 }}>
894
+ <OrbitRing
895
+ size={320}
896
+ rotation={-ringRot * 0.7}
897
+ tilt={65}
898
+ color="rgba(30,60,40,0.35)"
899
+ />
900
+ </div>
901
+ </div>
902
+
903
+ {/* Center logo — big droplet */}
904
+ <div
905
+ style={{
906
+ position: "absolute",
907
+ top: "18%",
908
+ left: "50%",
909
+ transform: `translate(-50%, ${logoFloat}px) scale(${interpolate(logoSpring, [0, 1], [0.3, 1])})`,
910
+ opacity: interpolate(logoSpring, [0, 0.2], [0, 1], {
911
+ extrapolateLeft: "clamp",
912
+ extrapolateRight: "clamp",
913
+ }),
914
+ }}
915
+ >
916
+ <JustDropShape size={200} rotation={logoRot} glow={0.5} />
917
+ </div>
918
+
919
+ {/* Screen recording — skills library demo */}
920
+ <div
921
+ style={{
922
+ position: "absolute",
923
+ top: "32%",
924
+ left: "50%",
925
+ width: "88%",
926
+ transform: `translate(-50%, 0) scale(${interpolate(videoSpring, [0, 1], [0.92, 1])})`,
927
+ opacity: interpolate(videoSpring, [0, 0.5], [0, 1], {
928
+ extrapolateLeft: "clamp",
929
+ extrapolateRight: "clamp",
930
+ }),
931
+ borderRadius: 20,
932
+ overflow: "hidden",
933
+ boxShadow: ds.shadow.cardLight,
934
+ border: "1px solid rgba(0,0,0,0.08)",
935
+ background: "#000",
936
+ aspectRatio: "1920 / 1024",
937
+ }}
938
+ >
939
+ <Sequence from={Math.round(SEGS[2].s * fps)}>
940
+ {/* REFERENCE-STRIP: <OffthreadVideo> removed — bring your own clip */}
941
+ </Sequence>
942
+ </div>
943
+
944
+ {/* Counter */}
945
+ <div
946
+ style={{
947
+ position: "absolute",
948
+ top: "60%",
949
+ width: "100%",
950
+ textAlign: "center",
951
+ opacity: countOp,
952
+ transform: `scale(${countScale})`,
953
+ }}
954
+ >
955
+ <div
956
+ style={{
957
+ fontFamily: FONT,
958
+ fontSize: 150,
959
+ fontWeight: ds.text.bold,
960
+ color: ds.color.claude,
961
+ lineHeight: 1,
962
+ letterSpacing: "-0.06em",
963
+ textShadow: "0 0 50px rgba(212,102,58,0.25)",
964
+ }}
965
+ >
966
+ {count.toLocaleString()}+
967
+ </div>
968
+ <div
969
+ style={{
970
+ fontFamily: FONT,
971
+ fontSize: 26,
972
+ fontWeight: ds.text.medium,
973
+ color: T.body,
974
+ marginTop: 10,
975
+ letterSpacing: 1.5,
976
+ }}
977
+ >
978
+ Claude skills, ready to drop in
979
+ </div>
980
+ </div>
981
+
982
+ {/* "Completely free" badge */}
983
+ <div
984
+ style={{
985
+ position: "absolute",
986
+ bottom: "24%",
987
+ left: "50%",
988
+ transform: `translate(-50%, 0) scale(${interpolate(freeSpring, [0, 1], [0.6, 1])})`,
989
+ opacity: interpolate(freeSpring, [0, 0.3], [0, 1], {
990
+ extrapolateLeft: "clamp",
991
+ extrapolateRight: "clamp",
992
+ }),
993
+ background: "rgba(30,122,69,0.15)",
994
+ border: "1.5px solid rgba(30,122,69,0.4)",
995
+ borderRadius: ds.radius.pill,
996
+ padding: "14px 32px",
997
+ display: "flex",
998
+ alignItems: "center",
999
+ gap: 12,
1000
+ }}
1001
+ >
1002
+ <div
1003
+ style={{
1004
+ width: 10,
1005
+ height: 10,
1006
+ borderRadius: "50%",
1007
+ background: T.success,
1008
+ boxShadow: `0 0 12px ${T.success}`,
1009
+ }}
1010
+ />
1011
+ <span
1012
+ style={{
1013
+ fontFamily: FONT,
1014
+ fontSize: 26,
1015
+ fontWeight: ds.text.bold,
1016
+ color: T.success,
1017
+ letterSpacing: 0.5,
1018
+ }}
1019
+ >
1020
+ Completely free
1021
+ </span>
1022
+ </div>
1023
+ </AbsoluteFill>
1024
+ );
1025
+ };
1026
+
1027
+ // ═══════════════════════════════════════════════════════════════
1028
+ // SCENE 4: EXAMPLES — category grid
1029
+ // ═══════════════════════════════════════════════════════════════
1030
+
1031
+ const CATEGORIES: { label: string; icon: "Brush" | "Beaker" | "Chart" | "Pen" }[] = [
1032
+ { label: "UI Design", icon: "Brush" },
1033
+ { label: "App Testing", icon: "Beaker" },
1034
+ { label: "SEO", icon: "Chart" },
1035
+ { label: "Writing", icon: "Pen" },
1036
+ ];
1037
+
1038
+ const ExamplesScene: React.FC<SP> = ({ frame, fps }) => {
1039
+ const op = so(frame, fps, SEGS[3].s, SEGS[3].e);
1040
+ if (op === 0) return null;
1041
+ const f = lf(frame, fps, SEGS[3].s);
1042
+
1043
+ const headOp = interpolate(f, [0.3 * fps, 0.9 * fps], [0, 1], {
1044
+ extrapolateLeft: "clamp",
1045
+ extrapolateRight: "clamp",
1046
+ });
1047
+
1048
+ return (
1049
+ <AbsoluteFill style={{ opacity: op }}>
1050
+ {/* Title */}
1051
+ <div
1052
+ style={{
1053
+ position: "absolute",
1054
+ top: "16%",
1055
+ width: "100%",
1056
+ textAlign: "center",
1057
+ padding: "0 48px",
1058
+ opacity: headOp,
1059
+ }}
1060
+ >
1061
+ <div
1062
+ style={{
1063
+ fontFamily: FONT,
1064
+ fontSize: 54,
1065
+ fontWeight: ds.text.bold,
1066
+ color: T.heading,
1067
+ letterSpacing: ds.text.tighter,
1068
+ lineHeight: 1.1,
1069
+ }}
1070
+ >
1071
+ Skills for{" "}
1072
+ <span style={{ color: ds.color.claude }}>everything</span>
1073
+ </div>
1074
+ </div>
1075
+
1076
+ {/* 2x2 grid */}
1077
+ <div
1078
+ style={{
1079
+ position: "absolute",
1080
+ top: "32%",
1081
+ left: "50%",
1082
+ transform: "translate(-50%, 0)",
1083
+ display: "grid",
1084
+ gridTemplateColumns: "1fr 1fr",
1085
+ gap: 22,
1086
+ width: "88%",
1087
+ maxWidth: 820,
1088
+ }}
1089
+ >
1090
+ {CATEGORIES.map((cat, i) => {
1091
+ const delay = Math.round((0.8 + i * 0.28) * fps);
1092
+ const s = spring({
1093
+ frame: f,
1094
+ fps,
1095
+ delay,
1096
+ config: { damping: 13, stiffness: 100 },
1097
+ });
1098
+ const fromX = i % 2 === 0 ? -60 : 60;
1099
+ const x = interpolate(s, [0, 1], [fromX, 0]);
1100
+ const y = interpolate(s, [0, 1], [30, 0]);
1101
+ const itemOp = interpolate(s, [0, 0.3], [0, 1], {
1102
+ extrapolateLeft: "clamp",
1103
+ extrapolateRight: "clamp",
1104
+ });
1105
+ const IconComp = Icon[cat.icon];
1106
+
1107
+ return (
1108
+ <div
1109
+ key={cat.label}
1110
+ style={{
1111
+ ...ds.card3d.cardStyle,
1112
+ padding: "28px 26px",
1113
+ display: "flex",
1114
+ flexDirection: "column",
1115
+ alignItems: "flex-start",
1116
+ gap: 18,
1117
+ opacity: itemOp,
1118
+ transform: `translate(${x}px, ${y}px) rotateX(2deg) rotateY(${i % 2 === 0 ? -2 : 2}deg)`,
1119
+ transformStyle: "preserve-3d",
1120
+ minHeight: 180,
1121
+ justifyContent: "space-between",
1122
+ }}
1123
+ >
1124
+ {/* Icon tile */}
1125
+ <div
1126
+ style={{
1127
+ width: 58,
1128
+ height: 58,
1129
+ borderRadius: 16,
1130
+ background: "rgba(212,102,58,0.18)",
1131
+ border: "1px solid rgba(212,102,58,0.3)",
1132
+ display: "flex",
1133
+ alignItems: "center",
1134
+ justifyContent: "center",
1135
+ }}
1136
+ >
1137
+ <IconComp color={ds.color.claude} size={30} />
1138
+ </div>
1139
+
1140
+ {/* Label + count */}
1141
+ <div>
1142
+ <div
1143
+ style={{
1144
+ fontFamily: FONT,
1145
+ fontSize: 26,
1146
+ fontWeight: ds.text.semibold,
1147
+ color: G.text,
1148
+ letterSpacing: -0.3,
1149
+ }}
1150
+ >
1151
+ {cat.label}
1152
+ </div>
1153
+ <div
1154
+ style={{
1155
+ fontFamily: MONO,
1156
+ fontSize: 13,
1157
+ fontWeight: ds.text.medium,
1158
+ color: G.textMuted,
1159
+ marginTop: 4,
1160
+ letterSpacing: 0.5,
1161
+ }}
1162
+ >
1163
+ {["214", "87", "132", "164"][i]} skills
1164
+ </div>
1165
+ </div>
1166
+ </div>
1167
+ );
1168
+ })}
1169
+ </div>
1170
+
1171
+ {/* Footer copy */}
1172
+ <div
1173
+ style={{
1174
+ position: "absolute",
1175
+ bottom: "24%",
1176
+ width: "100%",
1177
+ textAlign: "center",
1178
+ padding: "0 48px",
1179
+ opacity: interpolate(f, [4.5 * fps, 5.2 * fps], [0, 1], {
1180
+ extrapolateLeft: "clamp",
1181
+ extrapolateRight: "clamp",
1182
+ }),
1183
+ }}
1184
+ >
1185
+ <div
1186
+ style={{
1187
+ fontFamily: FONT,
1188
+ fontSize: 26,
1189
+ fontWeight: ds.text.regular,
1190
+ color: T.muted,
1191
+ fontStyle: "italic",
1192
+ lineHeight: 1.4,
1193
+ }}
1194
+ >
1195
+ Whatever you're building,
1196
+ <br />
1197
+ <span style={{ color: T.heading, fontStyle: "normal", fontWeight: ds.text.semibold }}>
1198
+ there's probably one for it.
1199
+ </span>
1200
+ </div>
1201
+ </div>
1202
+ </AbsoluteFill>
1203
+ );
1204
+ };
1205
+
1206
+ // ═══════════════════════════════════════════════════════════════
1207
+ // SCENE 5: PLATFORMS — "works everywhere"
1208
+ // ═══════════════════════════════════════════════════════════════
1209
+
1210
+ const PLATFORMS: { name: string; hint: string }[] = [
1211
+ { name: "Anti-Gravity", hint: "Google's AI editor" },
1212
+ { name: "Codex", hint: "OpenAI CLI" },
1213
+ { name: "Claude Desktop", hint: "Native app" },
1214
+ { name: "Any code editor", hint: "VS Code, Cursor, etc." },
1215
+ ];
1216
+
1217
+ const PlatformsScene: React.FC<SP> = ({ frame, fps }) => {
1218
+ const op = so(frame, fps, SEGS[4].s, SEGS[4].e);
1219
+ if (op === 0) return null;
1220
+ const f = lf(frame, fps, SEGS[4].s);
1221
+
1222
+ const headOp = interpolate(f, [0.3 * fps, 0.9 * fps], [0, 1], {
1223
+ extrapolateLeft: "clamp",
1224
+ extrapolateRight: "clamp",
1225
+ });
1226
+
1227
+ return (
1228
+ <AbsoluteFill style={{ opacity: op }}>
1229
+ {/* Title */}
1230
+ <div
1231
+ style={{
1232
+ position: "absolute",
1233
+ top: "16%",
1234
+ width: "100%",
1235
+ textAlign: "center",
1236
+ padding: "0 48px",
1237
+ opacity: headOp,
1238
+ }}
1239
+ >
1240
+ <div
1241
+ style={{
1242
+ fontFamily: FONT,
1243
+ fontSize: 22,
1244
+ fontWeight: ds.text.medium,
1245
+ color: T.muted,
1246
+ letterSpacing: ds.text.wider,
1247
+ textTransform: "uppercase",
1248
+ }}
1249
+ >
1250
+ One library
1251
+ </div>
1252
+ <div
1253
+ style={{
1254
+ fontFamily: FONT,
1255
+ fontSize: 56,
1256
+ fontWeight: ds.text.bold,
1257
+ color: T.heading,
1258
+ letterSpacing: ds.text.tighter,
1259
+ marginTop: 8,
1260
+ }}
1261
+ >
1262
+ Works{" "}
1263
+ <span style={{ color: ds.color.claude }}>everywhere</span>
1264
+ </div>
1265
+ </div>
1266
+
1267
+ {/* Platform list — floating 3D rows */}
1268
+ <div
1269
+ style={{
1270
+ position: "absolute",
1271
+ top: "32%",
1272
+ left: "50%",
1273
+ transform: "translate(-50%, 0)",
1274
+ display: "flex",
1275
+ flexDirection: "column",
1276
+ gap: 16,
1277
+ width: "88%",
1278
+ maxWidth: 800,
1279
+ }}
1280
+ >
1281
+ {PLATFORMS.map((plat, i) => {
1282
+ const delay = Math.round((0.8 + i * 0.3) * fps);
1283
+ const s = spring({
1284
+ frame: f,
1285
+ fps,
1286
+ delay,
1287
+ config: { damping: 14, stiffness: 100 },
1288
+ });
1289
+ const x = interpolate(s, [0, 1], [-80, 0]);
1290
+ const itemOp = interpolate(s, [0, 0.3], [0, 1], {
1291
+ extrapolateLeft: "clamp",
1292
+ extrapolateRight: "clamp",
1293
+ });
1294
+ // Check pops in after row lands
1295
+ const checkDelay = delay + Math.round(0.35 * fps);
1296
+ const cs = spring({
1297
+ frame: f,
1298
+ fps,
1299
+ delay: checkDelay,
1300
+ config: { damping: 8, stiffness: 120 },
1301
+ });
1302
+
1303
+ return (
1304
+ <div
1305
+ key={plat.name}
1306
+ style={{
1307
+ ...ds.card3d.cardStyle,
1308
+ padding: "20px 26px",
1309
+ display: "flex",
1310
+ alignItems: "center",
1311
+ justifyContent: "space-between",
1312
+ opacity: itemOp,
1313
+ transform: `translateX(${x}px) rotateY(-1deg)`,
1314
+ transformStyle: "preserve-3d",
1315
+ }}
1316
+ >
1317
+ <div style={{ display: "flex", alignItems: "center", gap: 16 }}>
1318
+ {/* Tile with platform initial */}
1319
+ <div
1320
+ style={{
1321
+ width: 52,
1322
+ height: 52,
1323
+ borderRadius: 14,
1324
+ background: "linear-gradient(145deg, rgba(255,255,255,0.08), rgba(0,0,0,0.3))",
1325
+ border: "1px solid rgba(255,255,255,0.08)",
1326
+ display: "flex",
1327
+ alignItems: "center",
1328
+ justifyContent: "center",
1329
+ fontFamily: MONO,
1330
+ fontSize: 22,
1331
+ fontWeight: ds.text.bold,
1332
+ color: ds.color.claude,
1333
+ }}
1334
+ >
1335
+ {plat.name[0]}
1336
+ </div>
1337
+ <div>
1338
+ <div
1339
+ style={{
1340
+ fontFamily: FONT,
1341
+ fontSize: 26,
1342
+ fontWeight: ds.text.semibold,
1343
+ color: G.text,
1344
+ }}
1345
+ >
1346
+ {plat.name}
1347
+ </div>
1348
+ <div
1349
+ style={{
1350
+ fontFamily: MONO,
1351
+ fontSize: 13,
1352
+ fontWeight: ds.text.regular,
1353
+ color: G.textMuted,
1354
+ marginTop: 2,
1355
+ letterSpacing: 0.3,
1356
+ }}
1357
+ >
1358
+ {plat.hint}
1359
+ </div>
1360
+ </div>
1361
+ </div>
1362
+
1363
+ {/* Checkmark pill */}
1364
+ <div
1365
+ style={{
1366
+ width: 40,
1367
+ height: 40,
1368
+ borderRadius: 12,
1369
+ background: "rgba(30,122,69,0.2)",
1370
+ border: "1px solid rgba(30,122,69,0.4)",
1371
+ display: "flex",
1372
+ alignItems: "center",
1373
+ justifyContent: "center",
1374
+ transform: `scale(${interpolate(cs, [0, 1], [0, 1])})`,
1375
+ }}
1376
+ >
1377
+ <Icon.Check color="#5be8a0" size={20} />
1378
+ </div>
1379
+ </div>
1380
+ );
1381
+ })}
1382
+ </div>
1383
+
1384
+ {/* Close line */}
1385
+ <div
1386
+ style={{
1387
+ position: "absolute",
1388
+ bottom: "24%",
1389
+ width: "100%",
1390
+ textAlign: "center",
1391
+ opacity: interpolate(f, [5 * fps, 5.7 * fps], [0, 1], {
1392
+ extrapolateLeft: "clamp",
1393
+ extrapolateRight: "clamp",
1394
+ }),
1395
+ }}
1396
+ >
1397
+ <div
1398
+ style={{
1399
+ fontFamily: FONT,
1400
+ fontSize: 30,
1401
+ fontWeight: ds.text.semibold,
1402
+ color: T.heading,
1403
+ fontStyle: "italic",
1404
+ }}
1405
+ >
1406
+ It just{" "}
1407
+ <span style={{ color: ds.color.claude, fontStyle: "normal" }}>works.</span>
1408
+ </div>
1409
+ </div>
1410
+ </AbsoluteFill>
1411
+ );
1412
+ };
1413
+
1414
+ // ═══════════════════════════════════════════════════════════════
1415
+ // SCENE 6: SETUP — 3D terminal walkthrough
1416
+ // ═══════════════════════════════════════════════════════════════
1417
+
1418
+ const SetupScene: React.FC<SP> = ({ frame, fps }) => {
1419
+ const op = so(frame, fps, SEGS[5].s, SEGS[5].e);
1420
+ if (op === 0) return null;
1421
+ const f = lf(frame, fps, SEGS[5].s);
1422
+
1423
+ const headOp = interpolate(f, [0.3 * fps, 0.9 * fps], [0, 1], {
1424
+ extrapolateLeft: "clamp",
1425
+ extrapolateRight: "clamp",
1426
+ });
1427
+
1428
+ const termSpring = spring({
1429
+ frame: f,
1430
+ fps,
1431
+ delay: Math.round(0.6 * fps),
1432
+ config: ds.spring.glass,
1433
+ });
1434
+
1435
+ // Typing progression for the command — uses the JustDrop brand, no URL reveal
1436
+ const cmdText = "justdrop install code-review";
1437
+ const cmdChars = Math.floor(
1438
+ interpolate(f, [1.5 * fps, 3 * fps], [0, cmdText.length], {
1439
+ extrapolateLeft: "clamp",
1440
+ extrapolateRight: "clamp",
1441
+ }),
1442
+ );
1443
+
1444
+ // Success state after typing
1445
+ const successOp = interpolate(f, [3.5 * fps, 4.2 * fps], [0, 1], {
1446
+ extrapolateLeft: "clamp",
1447
+ extrapolateRight: "clamp",
1448
+ });
1449
+
1450
+ // Final "You're good" celebration
1451
+ const doneSpring = spring({
1452
+ frame: f,
1453
+ fps,
1454
+ delay: Math.round(5.5 * fps),
1455
+ config: { damping: 8, stiffness: 110 },
1456
+ });
1457
+
1458
+ return (
1459
+ <AbsoluteFill style={{ opacity: op }}>
1460
+ {/* Title */}
1461
+ <div
1462
+ style={{
1463
+ position: "absolute",
1464
+ top: "16%",
1465
+ width: "100%",
1466
+ textAlign: "center",
1467
+ opacity: headOp,
1468
+ }}
1469
+ >
1470
+ <div
1471
+ style={{
1472
+ fontFamily: FONT,
1473
+ fontSize: 52,
1474
+ fontWeight: ds.text.bold,
1475
+ color: T.heading,
1476
+ letterSpacing: ds.text.tighter,
1477
+ lineHeight: 1.1,
1478
+ }}
1479
+ >
1480
+ Setup is{" "}
1481
+ <span style={{ color: ds.color.claude }}>nothing</span>
1482
+ </div>
1483
+ </div>
1484
+
1485
+ {/* 3D Terminal window */}
1486
+ <div
1487
+ style={{
1488
+ position: "absolute",
1489
+ top: "30%",
1490
+ left: "50%",
1491
+ transform: `translate(-50%, ${interpolate(termSpring, [0, 1], [60, 0])}px)`,
1492
+ opacity: interpolate(termSpring, [0, 0.3], [0, 1], {
1493
+ extrapolateLeft: "clamp",
1494
+ extrapolateRight: "clamp",
1495
+ }),
1496
+ width: "90%",
1497
+ maxWidth: 900,
1498
+ perspective: 1000,
1499
+ }}
1500
+ >
1501
+ <div
1502
+ style={{
1503
+ borderRadius: ds.radius.xl,
1504
+ overflow: "hidden",
1505
+ boxShadow: ds.card3d.shadowTable,
1506
+ transform: `rotateX(6deg) rotateY(-2deg) scale(${interpolate(termSpring, [0, 1], [0.9, 1])})`,
1507
+ border: "1px solid rgba(255,255,255,0.08)",
1508
+ background: "#0d1812",
1509
+ }}
1510
+ >
1511
+ {/* Terminal chrome */}
1512
+ <div
1513
+ style={{
1514
+ display: "flex",
1515
+ alignItems: "center",
1516
+ gap: 8,
1517
+ padding: "14px 18px",
1518
+ background: "linear-gradient(180deg, #1a2e1f, #142318)",
1519
+ borderBottom: "1px solid rgba(255,255,255,0.04)",
1520
+ }}
1521
+ >
1522
+ <div style={{ width: 12, height: 12, borderRadius: "50%", background: "#ff5f57" }} />
1523
+ <div style={{ width: 12, height: 12, borderRadius: "50%", background: "#febb2e" }} />
1524
+ <div style={{ width: 12, height: 12, borderRadius: "50%", background: "#28c840" }} />
1525
+ <div
1526
+ style={{
1527
+ marginLeft: 12,
1528
+ fontFamily: MONO,
1529
+ fontSize: 12,
1530
+ color: "rgba(255,255,255,0.35)",
1531
+ letterSpacing: 0.5,
1532
+ }}
1533
+ >
1534
+ terminal · ~/project
1535
+ </div>
1536
+ </div>
1537
+
1538
+ {/* Terminal body */}
1539
+ <div
1540
+ style={{
1541
+ padding: "28px 32px",
1542
+ minHeight: 320,
1543
+ fontFamily: MONO,
1544
+ fontSize: 20,
1545
+ lineHeight: 1.7,
1546
+ }}
1547
+ >
1548
+ {/* Prompt + command */}
1549
+ <div style={{ display: "flex", gap: 10, alignItems: "center" }}>
1550
+ <span style={{ color: "#5be8a0", fontWeight: ds.text.bold }}>$</span>
1551
+ <span style={{ color: "#fff" }}>
1552
+ {cmdText.slice(0, cmdChars)}
1553
+ {cmdChars < cmdText.length && (
1554
+ <span style={{ opacity: Math.sin(f * 0.4) > 0 ? 1 : 0, color: ds.color.claude }}>
1555
+
1556
+ </span>
1557
+ )}
1558
+ </span>
1559
+ </div>
1560
+
1561
+ {/* Success output */}
1562
+ <div style={{ opacity: successOp, marginTop: 14 }}>
1563
+ <div style={{ color: "rgba(255,255,255,0.55)", fontSize: 16 }}>
1564
+ → Fetching from JustDrop...
1565
+ </div>
1566
+ <div
1567
+ style={{
1568
+ color: "rgba(255,255,255,0.55)",
1569
+ fontSize: 16,
1570
+ opacity: interpolate(f, [4 * fps, 4.6 * fps], [0, 1], {
1571
+ extrapolateLeft: "clamp",
1572
+ extrapolateRight: "clamp",
1573
+ }),
1574
+ }}
1575
+ >
1576
+ → Picking agent: <span style={{ color: ds.color.claude }}>Claude Code</span>
1577
+ </div>
1578
+ <div
1579
+ style={{
1580
+ marginTop: 10,
1581
+ display: "flex",
1582
+ alignItems: "center",
1583
+ gap: 10,
1584
+ opacity: interpolate(f, [4.6 * fps, 5.3 * fps], [0, 1], {
1585
+ extrapolateLeft: "clamp",
1586
+ extrapolateRight: "clamp",
1587
+ }),
1588
+ }}
1589
+ >
1590
+ <span
1591
+ style={{
1592
+ display: "inline-flex",
1593
+ width: 22,
1594
+ height: 22,
1595
+ borderRadius: "50%",
1596
+ background: "rgba(30,122,69,0.25)",
1597
+ border: "1px solid rgba(91,232,160,0.5)",
1598
+ alignItems: "center",
1599
+ justifyContent: "center",
1600
+ }}
1601
+ >
1602
+ <Icon.Check color="#5be8a0" size={12} />
1603
+ </span>
1604
+ <span style={{ color: "#5be8a0", fontWeight: ds.text.semibold }}>
1605
+ Installed — code-review ready
1606
+ </span>
1607
+ </div>
1608
+ </div>
1609
+ </div>
1610
+ </div>
1611
+ </div>
1612
+
1613
+ {/* "You're good" */}
1614
+ <div
1615
+ style={{
1616
+ position: "absolute",
1617
+ bottom: "24%",
1618
+ width: "100%",
1619
+ textAlign: "center",
1620
+ transform: `scale(${interpolate(doneSpring, [0, 1], [0.7, 1])})`,
1621
+ opacity: interpolate(doneSpring, [0, 0.3], [0, 1], {
1622
+ extrapolateLeft: "clamp",
1623
+ extrapolateRight: "clamp",
1624
+ }),
1625
+ }}
1626
+ >
1627
+ <div
1628
+ style={{
1629
+ fontFamily: FONT,
1630
+ fontSize: 34,
1631
+ fontWeight: ds.text.bold,
1632
+ color: T.success,
1633
+ }}
1634
+ >
1635
+ That's it. You're good.
1636
+ </div>
1637
+ </div>
1638
+ </AbsoluteFill>
1639
+ );
1640
+ };
1641
+
1642
+ // ═══════════════════════════════════════════════════════════════
1643
+ // SCENE 7: CTA — "Drop Claude in comments"
1644
+ // ═══════════════════════════════════════════════════════════════
1645
+
1646
+ const CTAScene: React.FC<SP> = ({ frame, fps }) => {
1647
+ const op = so(frame, fps, SEGS[6].s, SEGS[6].e, 0.5, 0.6);
1648
+ if (op === 0) return null;
1649
+ const f = lf(frame, fps, SEGS[6].s);
1650
+
1651
+ const mainSpring = spring({
1652
+ frame: f,
1653
+ fps,
1654
+ delay: Math.round(0.2 * fps),
1655
+ config: { damping: 10, stiffness: 90 },
1656
+ });
1657
+
1658
+ // Comment bubble entry
1659
+ const bubbleSpring = spring({
1660
+ frame: f,
1661
+ fps,
1662
+ delay: Math.round(1.2 * fps),
1663
+ config: { damping: 11, stiffness: 95 },
1664
+ });
1665
+
1666
+ // Arrow bounce
1667
+ const arrowY = Math.sin((f / fps) * 3.5) * 10;
1668
+
1669
+ // Drop logo floats
1670
+ const floatY = Math.sin(f / fps * 2) * 6;
1671
+ const dropRot = interpolate(f, [0, 5.5 * fps], [0, 30]);
1672
+
1673
+ return (
1674
+ <AbsoluteFill style={{ opacity: op }}>
1675
+ {/* Drop shape watermark top */}
1676
+ <div
1677
+ style={{
1678
+ position: "absolute",
1679
+ top: "17%",
1680
+ left: "50%",
1681
+ transform: `translate(-50%, ${floatY}px) scale(${interpolate(mainSpring, [0, 1], [0.5, 1])})`,
1682
+ opacity: interpolate(mainSpring, [0, 0.3], [0, 1], {
1683
+ extrapolateLeft: "clamp",
1684
+ extrapolateRight: "clamp",
1685
+ }),
1686
+ }}
1687
+ >
1688
+ <JustDropShape size={110} rotation={dropRot} glow={0.45} />
1689
+ </div>
1690
+
1691
+ {/* Main instruction */}
1692
+ <div
1693
+ style={{
1694
+ position: "absolute",
1695
+ top: "34%",
1696
+ width: "100%",
1697
+ textAlign: "center",
1698
+ padding: "0 44px",
1699
+ transform: `scale(${interpolate(mainSpring, [0, 1], [0.88, 1])})`,
1700
+ opacity: interpolate(mainSpring, [0, 0.2], [0, 1], {
1701
+ extrapolateLeft: "clamp",
1702
+ extrapolateRight: "clamp",
1703
+ }),
1704
+ }}
1705
+ >
1706
+ <div
1707
+ style={{
1708
+ fontFamily: FONT,
1709
+ fontSize: 54,
1710
+ fontWeight: ds.text.bold,
1711
+ color: T.heading,
1712
+ letterSpacing: ds.text.tighter,
1713
+ lineHeight: 1.1,
1714
+ }}
1715
+ >
1716
+ Drop{" "}
1717
+ <span
1718
+ style={{
1719
+ color: ds.color.claude,
1720
+ textShadow: "0 0 30px rgba(212,102,58,0.3)",
1721
+ }}
1722
+ >
1723
+ "Claude"
1724
+ </span>
1725
+ <br />
1726
+ in the comments
1727
+ </div>
1728
+ </div>
1729
+
1730
+ {/* Comment bubble 3D mockup */}
1731
+ <div
1732
+ style={{
1733
+ position: "absolute",
1734
+ top: "55%",
1735
+ left: "50%",
1736
+ transform: `translate(-50%, 0) scale(${interpolate(bubbleSpring, [0, 1], [0.6, 1])})`,
1737
+ opacity: interpolate(bubbleSpring, [0, 0.3], [0, 1], {
1738
+ extrapolateLeft: "clamp",
1739
+ extrapolateRight: "clamp",
1740
+ }),
1741
+ width: "80%",
1742
+ maxWidth: 720,
1743
+ perspective: 1000,
1744
+ }}
1745
+ >
1746
+ <div
1747
+ style={{
1748
+ ...ds.card3d.cardStyle,
1749
+ padding: "22px 26px",
1750
+ transform: "rotateX(4deg) rotateY(-2deg)",
1751
+ transformStyle: "preserve-3d",
1752
+ display: "flex",
1753
+ alignItems: "center",
1754
+ gap: 16,
1755
+ }}
1756
+ >
1757
+ {/* Avatar circle */}
1758
+ <div
1759
+ style={{
1760
+ width: 52,
1761
+ height: 52,
1762
+ borderRadius: "50%",
1763
+ background: "linear-gradient(135deg, #D4663A, #8B3A1E)",
1764
+ display: "flex",
1765
+ alignItems: "center",
1766
+ justifyContent: "center",
1767
+ fontFamily: FONT,
1768
+ fontSize: 22,
1769
+ fontWeight: ds.text.bold,
1770
+ color: "#fff",
1771
+ flexShrink: 0,
1772
+ }}
1773
+ >
1774
+ <Icon.Chat color="#fff" size={24} />
1775
+ </div>
1776
+
1777
+ {/* Comment text */}
1778
+ <div style={{ flex: 1 }}>
1779
+ <div
1780
+ style={{
1781
+ fontFamily: FONT,
1782
+ fontSize: 13,
1783
+ color: G.textMuted,
1784
+ letterSpacing: 0.5,
1785
+ }}
1786
+ >
1787
+ you · just now
1788
+ </div>
1789
+ <div
1790
+ style={{
1791
+ fontFamily: FONT,
1792
+ fontSize: 28,
1793
+ fontWeight: ds.text.semibold,
1794
+ color: G.text,
1795
+ marginTop: 2,
1796
+ letterSpacing: -0.3,
1797
+ }}
1798
+ >
1799
+ Claude
1800
+ {f > 3 * fps && (
1801
+ <span
1802
+ style={{
1803
+ opacity: Math.sin(f * 0.3) > 0 ? 1 : 0,
1804
+ color: ds.color.claude,
1805
+ marginLeft: 2,
1806
+ }}
1807
+ >
1808
+ |
1809
+ </span>
1810
+ )}
1811
+ </div>
1812
+ </div>
1813
+
1814
+ {/* Send icon */}
1815
+ <div
1816
+ style={{
1817
+ width: 44,
1818
+ height: 44,
1819
+ borderRadius: 12,
1820
+ background: "rgba(212,102,58,0.22)",
1821
+ border: "1px solid rgba(212,102,58,0.45)",
1822
+ display: "flex",
1823
+ alignItems: "center",
1824
+ justifyContent: "center",
1825
+ flexShrink: 0,
1826
+ }}
1827
+ >
1828
+ <svg width={20} height={20} viewBox="0 0 24 24" fill="none">
1829
+ <path d="M3 12l18-8-5 18-4-8-9-2z" stroke={ds.color.claude} strokeWidth="1.8" strokeLinejoin="round" />
1830
+ </svg>
1831
+ </div>
1832
+ </div>
1833
+ </div>
1834
+
1835
+ {/* Bottom arrow + hint */}
1836
+ <div
1837
+ style={{
1838
+ position: "absolute",
1839
+ bottom: "26%",
1840
+ width: "100%",
1841
+ textAlign: "center",
1842
+ opacity: interpolate(f, [2.5 * fps, 3.2 * fps], [0, 1], {
1843
+ extrapolateLeft: "clamp",
1844
+ extrapolateRight: "clamp",
1845
+ }),
1846
+ }}
1847
+ >
1848
+ <div
1849
+ style={{
1850
+ display: "inline-flex",
1851
+ flexDirection: "column",
1852
+ alignItems: "center",
1853
+ gap: 8,
1854
+ }}
1855
+ >
1856
+ <div
1857
+ style={{
1858
+ fontFamily: FONT,
1859
+ fontSize: 20,
1860
+ fontWeight: ds.text.medium,
1861
+ color: T.muted,
1862
+ letterSpacing: 2,
1863
+ textTransform: "uppercase",
1864
+ }}
1865
+ >
1866
+ Link sent straight to you
1867
+ </div>
1868
+ <div style={{ transform: `translateY(${arrowY}px)`, color: ds.color.claude }}>
1869
+ <Icon.ArrowDown color={ds.color.claude} size={36} />
1870
+ </div>
1871
+ </div>
1872
+ </div>
1873
+ </AbsoluteFill>
1874
+ );
1875
+ };
1876
+
1877
+ // ═══════════════════════════════════════════════════════════════
1878
+ // MAIN COMPOSITION
1879
+ // ═══════════════════════════════════════════════════════════════
1880
+
1881
+ export const JustDropReel: React.FC = () => {
1882
+ const frame = useCurrentFrame();
1883
+ const { fps } = useVideoConfig();
1884
+
1885
+ return (
1886
+ <AbsoluteFill style={{ backgroundColor: "#e8e4de", fontFamily: FONT }}>
1887
+ {/* REFERENCE-STRIP: <Audio> tag removed — bring your own voiceover */}
1888
+ <Background frame={frame} />
1889
+ <HookScene frame={frame} fps={fps} />
1890
+ <PowersScene frame={frame} fps={fps} />
1891
+ <RevealScene frame={frame} fps={fps} />
1892
+ <ExamplesScene frame={frame} fps={fps} />
1893
+ <PlatformsScene frame={frame} fps={fps} />
1894
+ <SetupScene frame={frame} fps={fps} />
1895
+ <CTAScene frame={frame} fps={fps} />
1896
+ </AbsoluteFill>
1897
+ );
1898
+ };