@devinilabs/reelstack 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1799 @@
1
+ /**
2
+ * REFERENCE — Devini3DReel (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.4+
6
+ * for STUDY and as the source for the scaffold-from-reference flow.
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/Devini3DReel.tsx
15
+ * Bundled at: 2026-05-12T19:40:04.828Z
16
+ */
17
+ import { Fragment } from "react";
18
+ import {
19
+ useCurrentFrame,
20
+ useVideoConfig,
21
+ interpolate,
22
+ spring,
23
+ Easing,
24
+ AbsoluteFill,
25
+ Img,
26
+ OffthreadVideo,
27
+ Sequence,
28
+ staticFile,
29
+ } from "remotion";
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
+ const T = {
41
+ heading: "#1a1a1a",
42
+ body: "#2d2d2d",
43
+ muted: "#6b6b6b",
44
+ accent: ds.color.claude,
45
+ accentDark: "#1e3a27",
46
+ danger: "#c0392b",
47
+ success: "#1e7a45",
48
+ } as const;
49
+
50
+ // 8 scene segments aligned to 38s voiceover
51
+ // Clip stays visible for SEGS[0]..SEGS[5]; SEGS[6]+ is full motion graphics.
52
+ const SEGS = [
53
+ { s: 0, e: 4.5, text: "Hook" }, // 1: "this entire 3D website? Built in an hour"
54
+ { s: 4.0, e: 13.0, text: "WildPart" }, // 2: "custom Claude skill that handles everything"
55
+ { s: 12.5, e: 17.0, text: "Breadth" }, // 3: "from asset creation to execution"
56
+ { s: 16.5, e: 22.0, text: "Prompt" }, // 4: "I just told Claude what I wanted"
57
+ { s: 21.5, e: 27.0, text: "Pipeline" }, // 5: "3D models, code, scene, shipped"
58
+ { s: 26.5, e: 31.0, text: "NoStitching" }, // 6: "no asset packs, no boilerplate, no stitching"
59
+ { s: 30.5, e: 34.5, text: "OneSkill" }, // 7: "just one skill doing the whole pipeline" (motion graphics)
60
+ { s: 34.0, e: 38.5, text: "CTA" }, // 8: "Comment AI down below..."
61
+ ] as const;
62
+
63
+ // Clip visibility window — fades out as scene 7 begins
64
+ const CLIP_FADE_OUT_START = 30.5;
65
+ const CLIP_FADE_OUT_END = 31.5;
66
+
67
+ // ═══════════════════════════════════════════════════════════════
68
+ // UTILITIES
69
+ // ═══════════════════════════════════════════════════════════════
70
+
71
+ const so = (
72
+ frame: number,
73
+ fps: number,
74
+ startS: number,
75
+ endS: number,
76
+ fadeIn = 0.5,
77
+ fadeOut = 0.5,
78
+ ) =>
79
+ interpolate(
80
+ frame,
81
+ [startS * fps, (startS + fadeIn) * fps, (endS - fadeOut) * fps, endS * fps],
82
+ [0, 1, 1, 0],
83
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" },
84
+ );
85
+
86
+ const lf = (frame: number, fps: number, startSec: number) =>
87
+ Math.max(0, frame - startSec * fps);
88
+
89
+ interface SP {
90
+ frame: number;
91
+ fps: number;
92
+ }
93
+
94
+ // ═══════════════════════════════════════════════════════════════
95
+ // BACKGROUND — warm cream paper with subtle grid (matches JustDropReel)
96
+ // ═══════════════════════════════════════════════════════════════
97
+
98
+ const Background: React.FC<{ frame: number }> = ({ frame }) => {
99
+ const paperBase = "#e8e4de";
100
+ const paperLight = "#edeae4";
101
+ const gridLine = "rgba(180,175,165,0.25)";
102
+ const vignetteOp = 0.12 + Math.sin(frame * 0.006) * 0.03;
103
+
104
+ return (
105
+ <AbsoluteFill>
106
+ <div
107
+ style={{
108
+ width: "100%",
109
+ height: "100%",
110
+ background: `radial-gradient(ellipse at 50% 40%, ${paperLight} 0%, ${paperBase} 55%, #d9d4cc 100%)`,
111
+ }}
112
+ />
113
+ <div
114
+ style={{
115
+ position: "absolute",
116
+ inset: 0,
117
+ backgroundImage: `
118
+ linear-gradient(${gridLine} 1px, transparent 1px),
119
+ linear-gradient(90deg, ${gridLine} 1px, transparent 1px)
120
+ `,
121
+ backgroundSize: "48px 48px",
122
+ }}
123
+ />
124
+ <div
125
+ style={{
126
+ position: "absolute",
127
+ inset: 0,
128
+ background: `radial-gradient(ellipse at 50% 45%, rgba(255,255,255,0.3) 0%, transparent 60%)`,
129
+ }}
130
+ />
131
+ <div
132
+ style={{
133
+ position: "absolute",
134
+ inset: 0,
135
+ background: `radial-gradient(ellipse at 50% 50%, transparent 50%, rgba(0,0,0,${vignetteOp}) 100%)`,
136
+ }}
137
+ />
138
+ </AbsoluteFill>
139
+ );
140
+ };
141
+
142
+ // ═══════════════════════════════════════════════════════════════
143
+ // FLOATING CARD (reused from JustDropReel)
144
+ // ═══════════════════════════════════════════════════════════════
145
+
146
+ const FloatingCard: React.FC<{
147
+ children: React.ReactNode;
148
+ width?: number | string;
149
+ rotateX?: number;
150
+ rotateY?: number;
151
+ scale?: number;
152
+ opacity?: number;
153
+ style?: React.CSSProperties;
154
+ }> = ({
155
+ children,
156
+ width = "85%",
157
+ rotateX = 2,
158
+ rotateY = -1,
159
+ scale = 1,
160
+ opacity = 1,
161
+ style,
162
+ }) => (
163
+ <div
164
+ style={{
165
+ width,
166
+ maxWidth: 900,
167
+ perspective: 1200,
168
+ opacity,
169
+ ...style,
170
+ }}
171
+ >
172
+ <div
173
+ style={{
174
+ ...ds.card3d.cardStyle,
175
+ transform: `rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale(${scale})`,
176
+ transformStyle: "preserve-3d",
177
+ padding: "26px 30px",
178
+ position: "relative",
179
+ overflow: "hidden",
180
+ }}
181
+ >
182
+ <div
183
+ style={{
184
+ position: "absolute",
185
+ top: 0,
186
+ left: 20,
187
+ right: 20,
188
+ height: 1,
189
+ background:
190
+ "linear-gradient(90deg, transparent, rgba(255,255,255,0.12), transparent)",
191
+ }}
192
+ />
193
+ {children}
194
+ </div>
195
+ </div>
196
+ );
197
+
198
+ // ═══════════════════════════════════════════════════════════════
199
+ // 3D CLAUDE SKILL ICON (reused JustDropShape pattern)
200
+ // ═══════════════════════════════════════════════════════════════
201
+
202
+ const SkillIcon: React.FC<{
203
+ size: number;
204
+ rotation?: number;
205
+ glow?: number;
206
+ }> = ({ size, rotation = 0, glow = 0.4 }) => (
207
+ <div
208
+ style={{
209
+ width: size,
210
+ height: size,
211
+ position: "relative",
212
+ perspective: 800,
213
+ }}
214
+ >
215
+ <div
216
+ style={{
217
+ position: "absolute",
218
+ inset: 0,
219
+ transformStyle: "preserve-3d",
220
+ transform: `rotateY(${rotation}deg) rotateX(-8deg)`,
221
+ }}
222
+ >
223
+ <div
224
+ style={{
225
+ position: "absolute",
226
+ inset: -size * 0.2,
227
+ borderRadius: "50%",
228
+ background: `radial-gradient(circle, rgba(212,102,58,${glow}) 0%, transparent 60%)`,
229
+ filter: "blur(24px)",
230
+ }}
231
+ />
232
+ <Img
233
+ src={staticFile("claude-ai-icon.webp")}
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
+ // Orbital ring for hero scenes
246
+ const OrbitRing: React.FC<{
247
+ size: number;
248
+ rotation: number;
249
+ tilt: number;
250
+ color?: string;
251
+ }> = ({ size, rotation, tilt, color = "rgba(212,102,58,0.4)" }) => (
252
+ <div
253
+ style={{
254
+ width: size,
255
+ height: size,
256
+ borderRadius: "50%",
257
+ border: `1.5px solid ${color}`,
258
+ transform: `rotateX(${tilt}deg) rotateZ(${rotation}deg)`,
259
+ transformStyle: "preserve-3d",
260
+ }}
261
+ />
262
+ );
263
+
264
+ // ═══════════════════════════════════════════════════════════════
265
+ // ICONS (reused from JustDropReel)
266
+ // ═══════════════════════════════════════════════════════════════
267
+
268
+ const Icon = {
269
+ Cube: ({ color = "#fff", size = 24 }) => (
270
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
271
+ <path d="M12 3l8 4.5v9L12 21l-8-4.5v-9L12 3z" stroke={color} strokeWidth="1.7" strokeLinejoin="round" />
272
+ <path d="M4 7.5L12 12l8-4.5M12 12v9" stroke={color} strokeWidth="1.7" strokeLinejoin="round" />
273
+ </svg>
274
+ ),
275
+ Code: ({ color = "#fff", size = 24 }) => (
276
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
277
+ <path d="M8 6l-5 6 5 6M16 6l5 6-5 6M14 4l-4 16" stroke={color} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" />
278
+ </svg>
279
+ ),
280
+ Scene: ({ color = "#fff", size = 24 }) => (
281
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
282
+ <rect x="3" y="5" width="18" height="14" rx="2" stroke={color} strokeWidth="1.7" />
283
+ <path d="M3 10h18M9 5v14" stroke={color} strokeWidth="1.7" />
284
+ </svg>
285
+ ),
286
+ Ship: ({ color = "#fff", size = 24 }) => (
287
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
288
+ <path d="M12 3v18M5 10l7-7 7 7" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
289
+ </svg>
290
+ ),
291
+ Check: ({ color = "#fff", size = 20 }) => (
292
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
293
+ <path d="M5 12l5 5L20 7" stroke={color} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
294
+ </svg>
295
+ ),
296
+ Cross: ({ color = "#fff", size = 18 }) => (
297
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
298
+ <path d="M6 6l12 12M18 6L6 18" stroke={color} strokeWidth="2.4" strokeLinecap="round" />
299
+ </svg>
300
+ ),
301
+ Sparkle: ({ color = "#fff", size = 24 }) => (
302
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
303
+ <path d="M12 3l2 6 6 2-6 2-2 6-2-6-6-2 6-2 2-6z" fill={color} />
304
+ </svg>
305
+ ),
306
+ ArrowDown: ({ color = "#fff", size = 32 }) => (
307
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
308
+ <path d="M12 5v14M6 13l6 6 6-6" stroke={color} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" />
309
+ </svg>
310
+ ),
311
+ Chat: ({ color = "#fff", size = 28 }) => (
312
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
313
+ <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" />
314
+ </svg>
315
+ ),
316
+ Bolt: ({ color = "#fff", size = 24 }) => (
317
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
318
+ <path d="M13 2L4 14h7l-1 8 9-12h-7l1-8z" fill={color} />
319
+ </svg>
320
+ ),
321
+ };
322
+
323
+ // ═══════════════════════════════════════════════════════════════
324
+ // CLIP FRAME — the "showcase window" that holds the 3D website video
325
+ // Stays mounted for SEGS 0..5, fades out before SEGS[6] (OneSkill)
326
+ // ═══════════════════════════════════════════════════════════════
327
+
328
+ const ClipShowcase: React.FC<SP> = ({ frame, fps }) => {
329
+ // Master visibility — fade out at NoStitching → OneSkill boundary
330
+ const visibility = interpolate(
331
+ frame,
332
+ [0, 0.4 * fps, CLIP_FADE_OUT_START * fps, CLIP_FADE_OUT_END * fps],
333
+ [0, 1, 1, 0],
334
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" },
335
+ );
336
+ if (visibility === 0) return null;
337
+
338
+ // Entrance spring (gsap-style power3.out — "back.out(1.4)")
339
+ const entrance = spring({
340
+ frame,
341
+ fps,
342
+ delay: Math.round(0.15 * fps),
343
+ config: { damping: 12, stiffness: 95 },
344
+ });
345
+ const entranceScale = interpolate(entrance, [0, 1], [0.78, 1]);
346
+ const entranceY = interpolate(entrance, [0, 1], [60, 0]);
347
+
348
+ // Exit lift (during fade-out)
349
+ const exitLift = interpolate(
350
+ frame,
351
+ [CLIP_FADE_OUT_START * fps, CLIP_FADE_OUT_END * fps],
352
+ [0, -120],
353
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: Easing.in(Easing.cubic) },
354
+ );
355
+ const exitScale = interpolate(
356
+ frame,
357
+ [CLIP_FADE_OUT_START * fps, CLIP_FADE_OUT_END * fps],
358
+ [1, 0.85],
359
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" },
360
+ );
361
+
362
+ // Subtle float
363
+ const floatY = Math.sin((frame / fps) * 1.1) * 4;
364
+ const tiltSwing = Math.sin((frame / fps) * 0.6) * 1.2;
365
+
366
+ return (
367
+ <div
368
+ style={{
369
+ position: "absolute",
370
+ top: "26%",
371
+ left: "50%",
372
+ transform: `translate(-50%, ${entranceY + floatY + exitLift}px) scale(${entranceScale * exitScale})`,
373
+ opacity: visibility,
374
+ width: "92%",
375
+ maxWidth: 1000,
376
+ perspective: 1400,
377
+ }}
378
+ >
379
+ <div
380
+ style={{
381
+ borderRadius: 28,
382
+ overflow: "hidden",
383
+ boxShadow: [
384
+ "0 40px 80px -20px rgba(0,0,0,0.35)",
385
+ "0 18px 36px -8px rgba(0,0,0,0.22)",
386
+ "inset 0 1px 0 rgba(255,255,255,0.25)",
387
+ ].join(", "),
388
+ border: "1px solid rgba(0,0,0,0.10)",
389
+ background: "#0a0a0a",
390
+ aspectRatio: "3328 / 2160",
391
+ transform: `rotateX(${4 + tiltSwing * 0.4}deg) rotateY(${-2 + tiltSwing}deg)`,
392
+ transformStyle: "preserve-3d",
393
+ position: "relative",
394
+ }}
395
+ >
396
+ {/* Browser-like top bar */}
397
+ <div
398
+ style={{
399
+ position: "absolute",
400
+ top: 0,
401
+ left: 0,
402
+ right: 0,
403
+ height: 38,
404
+ background: "linear-gradient(180deg, rgba(255,255,255,0.10), rgba(0,0,0,0))",
405
+ borderBottom: "1px solid rgba(255,255,255,0.06)",
406
+ display: "flex",
407
+ alignItems: "center",
408
+ padding: "0 16px",
409
+ gap: 7,
410
+ zIndex: 2,
411
+ }}
412
+ >
413
+ <div style={{ width: 11, height: 11, borderRadius: "50%", background: "#ff5f57" }} />
414
+ <div style={{ width: 11, height: 11, borderRadius: "50%", background: "#febb2e" }} />
415
+ <div style={{ width: 11, height: 11, borderRadius: "50%", background: "#28c840" }} />
416
+ <div
417
+ style={{
418
+ marginLeft: 14,
419
+ fontFamily: MONO,
420
+ fontSize: 11,
421
+ color: "rgba(255,255,255,0.5)",
422
+ letterSpacing: 0.5,
423
+ }}
424
+ >
425
+ devini.io / 3d-website
426
+ </div>
427
+ </div>
428
+
429
+ {/* The video itself */}
430
+ <Sequence from={0}>
431
+ {/* REFERENCE-STRIP: <OffthreadVideo> removed — bring your own clip */}
432
+ </Sequence>
433
+
434
+ {/* Glassy reflection edge */}
435
+ <div
436
+ style={{
437
+ position: "absolute",
438
+ top: 38,
439
+ left: 0,
440
+ right: 0,
441
+ height: 30,
442
+ background: "linear-gradient(180deg, rgba(255,255,255,0.08), transparent)",
443
+ pointerEvents: "none",
444
+ }}
445
+ />
446
+ </div>
447
+ </div>
448
+ );
449
+ };
450
+
451
+ // ═══════════════════════════════════════════════════════════════
452
+ // SCENE 1: HOOK — "this entire 3D website? Built in an hour"
453
+ // ═══════════════════════════════════════════════════════════════
454
+
455
+ const HookScene: React.FC<SP> = ({ frame, fps }) => {
456
+ const op = so(frame, fps, SEGS[0].s, SEGS[0].e);
457
+ if (op === 0) return null;
458
+ const f = lf(frame, fps, SEGS[0].s);
459
+
460
+ // gsap-style timeline: badge → title → countdown
461
+ const badgeSpring = spring({
462
+ frame: f,
463
+ fps,
464
+ delay: Math.round(0.2 * fps),
465
+ config: ds.spring.bouncy,
466
+ });
467
+ const titleSpring = spring({
468
+ frame: f,
469
+ fps,
470
+ delay: Math.round(0.6 * fps),
471
+ config: ds.spring.gentle,
472
+ });
473
+ const titleScale = interpolate(titleSpring, [0, 1], [0.85, 1]);
474
+
475
+ // Bottom timer chip
476
+ const chipOp = interpolate(f, [1.6 * fps, 2.2 * fps], [0, 1], {
477
+ extrapolateLeft: "clamp",
478
+ extrapolateRight: "clamp",
479
+ });
480
+ const chipPulse = 1 + Math.sin((f / fps) * 4) * 0.04;
481
+
482
+ return (
483
+ <AbsoluteFill style={{ opacity: op, pointerEvents: "none" }}>
484
+ {/* Top pill badge */}
485
+ <div
486
+ style={{
487
+ position: "absolute",
488
+ top: "9.5%",
489
+ left: "50%",
490
+ transform: `translate(-50%, 0) scale(${interpolate(badgeSpring, [0, 1], [0.5, 1])})`,
491
+ opacity: interpolate(badgeSpring, [0, 0.3], [0, 1], {
492
+ extrapolateLeft: "clamp",
493
+ extrapolateRight: "clamp",
494
+ }),
495
+ ...ds.card3d.pillStyle,
496
+ display: "flex",
497
+ alignItems: "center",
498
+ gap: 10,
499
+ }}
500
+ >
501
+ <div
502
+ style={{
503
+ width: 8,
504
+ height: 8,
505
+ borderRadius: "50%",
506
+ background: ds.color.claude,
507
+ boxShadow: `0 0 10px ${ds.color.claude}`,
508
+ }}
509
+ />
510
+ <span
511
+ style={{
512
+ fontFamily: MONO,
513
+ fontSize: 14,
514
+ fontWeight: ds.text.medium,
515
+ color: G.text,
516
+ letterSpacing: 1.2,
517
+ }}
518
+ >
519
+ BUILT WITH CLAUDE
520
+ </span>
521
+ </div>
522
+
523
+ {/* Big headline — pinned high so the clip below reads underneath */}
524
+ <div
525
+ style={{
526
+ position: "absolute",
527
+ top: "13.5%",
528
+ width: "100%",
529
+ textAlign: "center",
530
+ padding: "0 48px",
531
+ transform: `scale(${titleScale})`,
532
+ opacity: interpolate(titleSpring, [0, 0.2], [0, 1], {
533
+ extrapolateLeft: "clamp",
534
+ extrapolateRight: "clamp",
535
+ }),
536
+ }}
537
+ >
538
+ <div
539
+ style={{
540
+ fontFamily: FONT,
541
+ fontSize: 56,
542
+ fontWeight: ds.text.bold,
543
+ color: T.heading,
544
+ lineHeight: 1.04,
545
+ letterSpacing: ds.text.tighter,
546
+ }}
547
+ >
548
+ This whole 3D site?{" "}
549
+ <span style={{ color: ds.color.claude }}>1 hour.</span>
550
+ </div>
551
+ </div>
552
+
553
+ {/* Bottom timer chip */}
554
+ <div
555
+ style={{
556
+ position: "absolute",
557
+ bottom: "23%",
558
+ left: "50%",
559
+ transform: `translate(-50%, 0) scale(${chipPulse})`,
560
+ opacity: chipOp,
561
+ display: "flex",
562
+ alignItems: "center",
563
+ gap: 10,
564
+ background: "rgba(212,102,58,0.14)",
565
+ border: "1.5px solid rgba(212,102,58,0.4)",
566
+ borderRadius: ds.radius.pill,
567
+ padding: "12px 24px",
568
+ }}
569
+ >
570
+ <Icon.Bolt color={ds.color.claude} size={20} />
571
+ <span
572
+ style={{
573
+ fontFamily: FONT,
574
+ fontSize: 22,
575
+ fontWeight: ds.text.semibold,
576
+ color: ds.color.claude,
577
+ letterSpacing: 0.4,
578
+ }}
579
+ >
580
+ start to ship · 60 minutes
581
+ </span>
582
+ </div>
583
+ </AbsoluteFill>
584
+ );
585
+ };
586
+
587
+ // ═══════════════════════════════════════════════════════════════
588
+ // SCENE 2: WILD PART — "custom Claude skill that handles everything"
589
+ // ═══════════════════════════════════════════════════════════════
590
+
591
+ const WildPartScene: React.FC<SP> = ({ frame, fps }) => {
592
+ const op = so(frame, fps, SEGS[1].s, SEGS[1].e);
593
+ if (op === 0) return null;
594
+ const f = lf(frame, fps, SEGS[1].s);
595
+
596
+ // gsap timeline: header pop → skill tag drops → "everything" emphasis
597
+ const headerOp = interpolate(f, [0.2 * fps, 0.7 * fps], [0, 1], {
598
+ extrapolateLeft: "clamp",
599
+ extrapolateRight: "clamp",
600
+ });
601
+ const headerY = interpolate(f, [0.2 * fps, 0.7 * fps], [-20, 0], {
602
+ extrapolateLeft: "clamp",
603
+ extrapolateRight: "clamp",
604
+ easing: Easing.out(Easing.cubic),
605
+ });
606
+
607
+ const tagSpring = spring({
608
+ frame: f,
609
+ fps,
610
+ delay: Math.round(0.9 * fps),
611
+ config: ds.spring.bouncy,
612
+ });
613
+
614
+ // The "EVERYTHING" emphasis stamp
615
+ const stampSpring = spring({
616
+ frame: f,
617
+ fps,
618
+ delay: Math.round(5.2 * fps),
619
+ config: { damping: 7, stiffness: 110 },
620
+ });
621
+ const stampRotate = interpolate(stampSpring, [0, 1], [-25, -8]);
622
+
623
+ // Bottom bar reveal
624
+ const barOp = interpolate(f, [2.2 * fps, 2.9 * fps], [0, 1], {
625
+ extrapolateLeft: "clamp",
626
+ extrapolateRight: "clamp",
627
+ });
628
+
629
+ return (
630
+ <AbsoluteFill style={{ opacity: op, pointerEvents: "none" }}>
631
+ {/* Top label */}
632
+ <div
633
+ style={{
634
+ position: "absolute",
635
+ top: "10%",
636
+ width: "100%",
637
+ textAlign: "center",
638
+ opacity: headerOp,
639
+ transform: `translateY(${headerY}px)`,
640
+ }}
641
+ >
642
+ <div
643
+ style={{
644
+ fontFamily: FONT,
645
+ fontSize: 18,
646
+ fontWeight: ds.text.medium,
647
+ color: T.muted,
648
+ letterSpacing: ds.text.wider,
649
+ textTransform: "uppercase",
650
+ }}
651
+ >
652
+ here's the wild part
653
+ </div>
654
+ </div>
655
+
656
+ {/* Headline */}
657
+ <div
658
+ style={{
659
+ position: "absolute",
660
+ top: "13%",
661
+ width: "100%",
662
+ textAlign: "center",
663
+ padding: "0 56px",
664
+ opacity: headerOp,
665
+ transform: `translateY(${headerY}px)`,
666
+ }}
667
+ >
668
+ <div
669
+ style={{
670
+ fontFamily: FONT,
671
+ fontSize: 48,
672
+ fontWeight: ds.text.bold,
673
+ color: T.heading,
674
+ lineHeight: 1.05,
675
+ letterSpacing: ds.text.tighter,
676
+ }}
677
+ >
678
+ One{" "}
679
+ <span style={{ color: ds.color.claude }}>custom skill</span>
680
+ <br />
681
+ handles it all
682
+ </div>
683
+ </div>
684
+
685
+ {/* "EVERYTHING" stamp — appears later in the scene */}
686
+ <div
687
+ style={{
688
+ position: "absolute",
689
+ top: "22.2%",
690
+ right: "8%",
691
+ transform: `rotate(${stampRotate}deg) scale(${interpolate(stampSpring, [0, 1], [0.4, 1])})`,
692
+ opacity: interpolate(stampSpring, [0, 0.3], [0, 1], {
693
+ extrapolateLeft: "clamp",
694
+ extrapolateRight: "clamp",
695
+ }),
696
+ background: "rgba(192,52,43,0.12)",
697
+ border: "2.5px dashed #c0392b",
698
+ borderRadius: 8,
699
+ padding: "6px 14px",
700
+ fontFamily: MONO,
701
+ fontSize: 18,
702
+ fontWeight: ds.text.bold,
703
+ color: "#c0392b",
704
+ letterSpacing: 1.5,
705
+ }}
706
+ >
707
+ EVERYTHING
708
+ </div>
709
+
710
+ {/* Bottom skill tag — anchored under the clip */}
711
+ <div
712
+ style={{
713
+ position: "absolute",
714
+ bottom: "23%",
715
+ left: "50%",
716
+ transform: `translate(-50%, ${interpolate(tagSpring, [0, 1], [40, 0])}px) scale(${interpolate(tagSpring, [0, 1], [0.7, 1])})`,
717
+ opacity: interpolate(tagSpring, [0, 0.3], [0, 1], {
718
+ extrapolateLeft: "clamp",
719
+ extrapolateRight: "clamp",
720
+ }),
721
+ display: "flex",
722
+ alignItems: "center",
723
+ gap: 12,
724
+ background: "rgba(0,0,0,0.85)",
725
+ border: "1px solid rgba(212,102,58,0.4)",
726
+ borderRadius: ds.radius.pill,
727
+ padding: "13px 22px",
728
+ }}
729
+ >
730
+ <Icon.Sparkle color={ds.color.claude} size={18} />
731
+ <span
732
+ style={{
733
+ fontFamily: MONO,
734
+ fontSize: 16,
735
+ fontWeight: ds.text.semibold,
736
+ color: "#fff",
737
+ letterSpacing: 0.5,
738
+ }}
739
+ >
740
+ /3d-website-builder
741
+ </span>
742
+ <span
743
+ style={{
744
+ fontFamily: MONO,
745
+ fontSize: 12,
746
+ fontWeight: ds.text.medium,
747
+ color: ds.color.claude,
748
+ background: "rgba(212,102,58,0.18)",
749
+ padding: "3px 9px",
750
+ borderRadius: ds.radius.sm,
751
+ letterSpacing: 0.8,
752
+ }}
753
+ >
754
+ CUSTOM
755
+ </span>
756
+ </div>
757
+
758
+ {/* Subtle bottom strap */}
759
+ <div
760
+ style={{
761
+ position: "absolute",
762
+ bottom: "19%",
763
+ width: "100%",
764
+ textAlign: "center",
765
+ opacity: barOp,
766
+ }}
767
+ >
768
+ <div
769
+ style={{
770
+ fontFamily: FONT,
771
+ fontSize: 16,
772
+ fontWeight: ds.text.medium,
773
+ color: T.muted,
774
+ fontStyle: "italic",
775
+ }}
776
+ >
777
+ built once · runs forever
778
+ </div>
779
+ </div>
780
+ </AbsoluteFill>
781
+ );
782
+ };
783
+
784
+ // ═══════════════════════════════════════════════════════════════
785
+ // SCENE 3: BREADTH — "from asset creation to execution"
786
+ // Pipeline arrows: ASSETS → CODE → SCENE → SHIP
787
+ // ═══════════════════════════════════════════════════════════════
788
+
789
+ const PIPELINE = [
790
+ { label: "Assets", icon: "Cube" as const },
791
+ { label: "Code", icon: "Code" as const },
792
+ { label: "Scene", icon: "Scene" as const },
793
+ { label: "Ship", icon: "Ship" as const },
794
+ ];
795
+
796
+ const BreadthScene: React.FC<SP> = ({ frame, fps }) => {
797
+ const op = so(frame, fps, SEGS[2].s, SEGS[2].e);
798
+ if (op === 0) return null;
799
+ const f = lf(frame, fps, SEGS[2].s);
800
+
801
+ const headOp = interpolate(f, [0.2 * fps, 0.7 * fps], [0, 1], {
802
+ extrapolateLeft: "clamp",
803
+ extrapolateRight: "clamp",
804
+ });
805
+
806
+ return (
807
+ <AbsoluteFill style={{ opacity: op, pointerEvents: "none" }}>
808
+ {/* Title */}
809
+ <div
810
+ style={{
811
+ position: "absolute",
812
+ top: "10.5%",
813
+ width: "100%",
814
+ textAlign: "center",
815
+ padding: "0 48px",
816
+ opacity: headOp,
817
+ }}
818
+ >
819
+ <div
820
+ style={{
821
+ fontFamily: FONT,
822
+ fontSize: 44,
823
+ fontWeight: ds.text.bold,
824
+ color: T.heading,
825
+ letterSpacing: ds.text.tighter,
826
+ lineHeight: 1.05,
827
+ }}
828
+ >
829
+ From <span style={{ color: ds.color.claude }}>assets</span>
830
+ {" → "}
831
+ <span style={{ color: ds.color.claude }}>execution</span>
832
+ </div>
833
+ </div>
834
+
835
+ {/* Pipeline pills under the clip */}
836
+ <div
837
+ style={{
838
+ position: "absolute",
839
+ bottom: "22%",
840
+ left: "50%",
841
+ transform: "translate(-50%, 0)",
842
+ display: "flex",
843
+ alignItems: "center",
844
+ gap: 8,
845
+ width: "92%",
846
+ maxWidth: 980,
847
+ justifyContent: "center",
848
+ }}
849
+ >
850
+ {PIPELINE.map((step, i) => {
851
+ const delay = Math.round((0.6 + i * 0.32) * fps);
852
+ const s = spring({
853
+ frame: f,
854
+ fps,
855
+ delay,
856
+ config: { damping: 12, stiffness: 110 },
857
+ });
858
+ const sc = interpolate(s, [0, 1], [0.4, 1]);
859
+ const itemOp = interpolate(s, [0, 0.3], [0, 1], {
860
+ extrapolateLeft: "clamp",
861
+ extrapolateRight: "clamp",
862
+ });
863
+ const IconComp = Icon[step.icon];
864
+
865
+ // Arrow between items
866
+ const arrowDelay = delay + Math.round(0.18 * fps);
867
+ const arrowOp = interpolate(
868
+ f,
869
+ [arrowDelay, arrowDelay + 0.3 * fps],
870
+ [0, 1],
871
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" },
872
+ );
873
+
874
+ return (
875
+ <Fragment key={step.label}>
876
+ <div
877
+ style={{
878
+ ...ds.card3d.cardStyle,
879
+ padding: "12px 14px",
880
+ display: "flex",
881
+ flexDirection: "column",
882
+ alignItems: "center",
883
+ gap: 6,
884
+ opacity: itemOp,
885
+ transform: `scale(${sc})`,
886
+ minWidth: 100,
887
+ }}
888
+ >
889
+ <div
890
+ style={{
891
+ width: 38,
892
+ height: 38,
893
+ borderRadius: 11,
894
+ background: "rgba(212,102,58,0.18)",
895
+ border: "1px solid rgba(212,102,58,0.3)",
896
+ display: "flex",
897
+ alignItems: "center",
898
+ justifyContent: "center",
899
+ }}
900
+ >
901
+ <IconComp color={ds.color.claude} size={20} />
902
+ </div>
903
+ <div
904
+ style={{
905
+ fontFamily: MONO,
906
+ fontSize: 12,
907
+ fontWeight: ds.text.semibold,
908
+ color: G.text,
909
+ letterSpacing: 0.6,
910
+ textTransform: "uppercase",
911
+ }}
912
+ >
913
+ {step.label}
914
+ </div>
915
+ </div>
916
+ {i < PIPELINE.length - 1 && (
917
+ <div
918
+ style={{
919
+ fontFamily: MONO,
920
+ fontSize: 24,
921
+ fontWeight: ds.text.bold,
922
+ color: ds.color.claude,
923
+ opacity: arrowOp,
924
+ }}
925
+ >
926
+
927
+ </div>
928
+ )}
929
+ </Fragment>
930
+ );
931
+ })}
932
+ </div>
933
+ </AbsoluteFill>
934
+ );
935
+ };
936
+
937
+ // ═══════════════════════════════════════════════════════════════
938
+ // SCENE 4: PROMPT — "I just told Claude what I wanted"
939
+ // ═══════════════════════════════════════════════════════════════
940
+
941
+ const PromptScene: React.FC<SP> = ({ frame, fps }) => {
942
+ const op = so(frame, fps, SEGS[3].s, SEGS[3].e);
943
+ if (op === 0) return null;
944
+ const f = lf(frame, fps, SEGS[3].s);
945
+
946
+ const headOp = interpolate(f, [0.2 * fps, 0.7 * fps], [0, 1], {
947
+ extrapolateLeft: "clamp",
948
+ extrapolateRight: "clamp",
949
+ });
950
+
951
+ const cardSpring = spring({
952
+ frame: f,
953
+ fps,
954
+ delay: Math.round(0.9 * fps),
955
+ config: ds.spring.glass,
956
+ });
957
+
958
+ // Type out the prompt
959
+ const promptText = "build me a 3D website";
960
+ const typedChars = Math.floor(
961
+ interpolate(f, [1.8 * fps, 3.4 * fps], [0, promptText.length], {
962
+ extrapolateLeft: "clamp",
963
+ extrapolateRight: "clamp",
964
+ }),
965
+ );
966
+
967
+ return (
968
+ <AbsoluteFill style={{ opacity: op, pointerEvents: "none" }}>
969
+ {/* Heading */}
970
+ <div
971
+ style={{
972
+ position: "absolute",
973
+ top: "11%",
974
+ width: "100%",
975
+ textAlign: "center",
976
+ opacity: headOp,
977
+ }}
978
+ >
979
+ <div
980
+ style={{
981
+ fontFamily: FONT,
982
+ fontSize: 42,
983
+ fontWeight: ds.text.bold,
984
+ color: T.heading,
985
+ letterSpacing: ds.text.tighter,
986
+ }}
987
+ >
988
+ I just <span style={{ color: ds.color.claude }}>asked</span>.
989
+ </div>
990
+ </div>
991
+
992
+ {/* Prompt card */}
993
+ <div
994
+ style={{
995
+ position: "absolute",
996
+ bottom: "22%",
997
+ left: "50%",
998
+ transform: `translate(-50%, ${interpolate(cardSpring, [0, 1], [40, 0])}px)`,
999
+ opacity: interpolate(cardSpring, [0, 0.3], [0, 1], {
1000
+ extrapolateLeft: "clamp",
1001
+ extrapolateRight: "clamp",
1002
+ }),
1003
+ width: "92%",
1004
+ maxWidth: 880,
1005
+ }}
1006
+ >
1007
+ <FloatingCard rotateX={3} rotateY={-1}>
1008
+ <div
1009
+ style={{
1010
+ display: "flex",
1011
+ alignItems: "center",
1012
+ gap: 14,
1013
+ padding: "4px 0",
1014
+ }}
1015
+ >
1016
+ <div
1017
+ style={{
1018
+ width: 42,
1019
+ height: 42,
1020
+ borderRadius: "50%",
1021
+ background: "linear-gradient(135deg, #D4663A, #8B3A1E)",
1022
+ display: "flex",
1023
+ alignItems: "center",
1024
+ justifyContent: "center",
1025
+ flexShrink: 0,
1026
+ }}
1027
+ >
1028
+ <Img
1029
+ src={staticFile("claude-ai-icon.webp")}
1030
+ style={{ width: 26, height: 26, display: "block" }}
1031
+ />
1032
+ </div>
1033
+ <div style={{ flex: 1 }}>
1034
+ <div
1035
+ style={{
1036
+ fontFamily: MONO,
1037
+ fontSize: 11,
1038
+ color: G.textMuted,
1039
+ letterSpacing: 1.2,
1040
+ marginBottom: 2,
1041
+ }}
1042
+ >
1043
+ YOU → CLAUDE
1044
+ </div>
1045
+ <div
1046
+ style={{
1047
+ fontFamily: FONT,
1048
+ fontSize: 24,
1049
+ fontWeight: ds.text.semibold,
1050
+ color: G.text,
1051
+ letterSpacing: -0.2,
1052
+ }}
1053
+ >
1054
+ {promptText.slice(0, typedChars)}
1055
+ {typedChars < promptText.length && (
1056
+ <span
1057
+ style={{
1058
+ opacity: Math.sin(f * 0.4) > 0 ? 1 : 0,
1059
+ color: ds.color.claude,
1060
+ marginLeft: 2,
1061
+ }}
1062
+ >
1063
+
1064
+ </span>
1065
+ )}
1066
+ </div>
1067
+ </div>
1068
+ </div>
1069
+ </FloatingCard>
1070
+ </div>
1071
+ </AbsoluteFill>
1072
+ );
1073
+ };
1074
+
1075
+ // ═══════════════════════════════════════════════════════════════
1076
+ // SCENE 5: PIPELINE — "3D models, code, scene, shipped" (checklist)
1077
+ // ═══════════════════════════════════════════════════════════════
1078
+
1079
+ const PIPELINE_STEPS = [
1080
+ "Generated the 3D models",
1081
+ "Wrote the code",
1082
+ "Set up the scene",
1083
+ "Shipped it",
1084
+ ];
1085
+
1086
+ const PipelineScene: React.FC<SP> = ({ frame, fps }) => {
1087
+ const op = so(frame, fps, SEGS[4].s, SEGS[4].e);
1088
+ if (op === 0) return null;
1089
+ const f = lf(frame, fps, SEGS[4].s);
1090
+
1091
+ const headOp = interpolate(f, [0.2 * fps, 0.7 * fps], [0, 1], {
1092
+ extrapolateLeft: "clamp",
1093
+ extrapolateRight: "clamp",
1094
+ });
1095
+
1096
+ return (
1097
+ <AbsoluteFill style={{ opacity: op, pointerEvents: "none" }}>
1098
+ {/* Title */}
1099
+ <div
1100
+ style={{
1101
+ position: "absolute",
1102
+ top: "10.5%",
1103
+ width: "100%",
1104
+ textAlign: "center",
1105
+ opacity: headOp,
1106
+ }}
1107
+ >
1108
+ <div
1109
+ style={{
1110
+ fontFamily: FONT,
1111
+ fontSize: 38,
1112
+ fontWeight: ds.text.bold,
1113
+ color: T.heading,
1114
+ letterSpacing: ds.text.tighter,
1115
+ lineHeight: 1.05,
1116
+ }}
1117
+ >
1118
+ And it just{" "}
1119
+ <span style={{ color: ds.color.claude }}>did it</span>
1120
+ </div>
1121
+ </div>
1122
+
1123
+ {/* Checklist below clip */}
1124
+ <div
1125
+ style={{
1126
+ position: "absolute",
1127
+ bottom: "18%",
1128
+ left: "50%",
1129
+ transform: "translate(-50%, 0)",
1130
+ display: "flex",
1131
+ flexDirection: "column",
1132
+ gap: 8,
1133
+ width: "88%",
1134
+ maxWidth: 820,
1135
+ }}
1136
+ >
1137
+ {PIPELINE_STEPS.map((step, i) => {
1138
+ const delay = Math.round((0.4 + i * 0.55) * fps);
1139
+ const s = spring({
1140
+ frame: f,
1141
+ fps,
1142
+ delay,
1143
+ config: { damping: 14, stiffness: 110 },
1144
+ });
1145
+ const x = interpolate(s, [0, 1], [-50, 0]);
1146
+ const itemOp = interpolate(s, [0, 0.3], [0, 1], {
1147
+ extrapolateLeft: "clamp",
1148
+ extrapolateRight: "clamp",
1149
+ });
1150
+ const checkDelay = delay + Math.round(0.25 * fps);
1151
+ const cs = spring({
1152
+ frame: f,
1153
+ fps,
1154
+ delay: checkDelay,
1155
+ config: { damping: 8, stiffness: 130 },
1156
+ });
1157
+
1158
+ return (
1159
+ <div
1160
+ key={step}
1161
+ style={{
1162
+ ...ds.card3d.cardStyle,
1163
+ padding: "12px 18px",
1164
+ display: "flex",
1165
+ alignItems: "center",
1166
+ gap: 14,
1167
+ opacity: itemOp,
1168
+ transform: `translateX(${x}px)`,
1169
+ minHeight: 50,
1170
+ }}
1171
+ >
1172
+ <div
1173
+ style={{
1174
+ width: 30,
1175
+ height: 30,
1176
+ borderRadius: 9,
1177
+ background: "rgba(30,122,69,0.22)",
1178
+ border: "1px solid rgba(30,122,69,0.45)",
1179
+ display: "flex",
1180
+ alignItems: "center",
1181
+ justifyContent: "center",
1182
+ transform: `scale(${interpolate(cs, [0, 1], [0, 1])})`,
1183
+ flexShrink: 0,
1184
+ }}
1185
+ >
1186
+ <Icon.Check color="#5be8a0" size={16} />
1187
+ </div>
1188
+ <span
1189
+ style={{
1190
+ fontFamily: FONT,
1191
+ fontSize: 22,
1192
+ fontWeight: ds.text.semibold,
1193
+ color: G.text,
1194
+ letterSpacing: -0.2,
1195
+ }}
1196
+ >
1197
+ {step}
1198
+ </span>
1199
+ </div>
1200
+ );
1201
+ })}
1202
+ </div>
1203
+ </AbsoluteFill>
1204
+ );
1205
+ };
1206
+
1207
+ // ═══════════════════════════════════════════════════════════════
1208
+ // SCENE 6: NO STITCHING — "no asset packs, no boilerplate, no stitching"
1209
+ // ═══════════════════════════════════════════════════════════════
1210
+
1211
+ const NEGATIVES = ["asset packs", "boilerplate", "stitching things together"];
1212
+
1213
+ const NoStitchingScene: React.FC<SP> = ({ frame, fps }) => {
1214
+ const op = so(frame, fps, SEGS[5].s, SEGS[5].e);
1215
+ if (op === 0) return null;
1216
+ const f = lf(frame, fps, SEGS[5].s);
1217
+
1218
+ const headOp = interpolate(f, [0.2 * fps, 0.7 * fps], [0, 1], {
1219
+ extrapolateLeft: "clamp",
1220
+ extrapolateRight: "clamp",
1221
+ });
1222
+
1223
+ return (
1224
+ <AbsoluteFill style={{ opacity: op, pointerEvents: "none" }}>
1225
+ {/* Title */}
1226
+ <div
1227
+ style={{
1228
+ position: "absolute",
1229
+ top: "11%",
1230
+ width: "100%",
1231
+ textAlign: "center",
1232
+ opacity: headOp,
1233
+ }}
1234
+ >
1235
+ <div
1236
+ style={{
1237
+ fontFamily: FONT,
1238
+ fontSize: 42,
1239
+ fontWeight: ds.text.bold,
1240
+ color: T.heading,
1241
+ letterSpacing: ds.text.tighter,
1242
+ }}
1243
+ >
1244
+ And{" "}
1245
+ <span style={{ color: T.danger }}>none</span>{" "}
1246
+ of the usual mess
1247
+ </div>
1248
+ </div>
1249
+
1250
+ {/* Stack of strikethrough cards */}
1251
+ <div
1252
+ style={{
1253
+ position: "absolute",
1254
+ bottom: "21%",
1255
+ left: "50%",
1256
+ transform: "translate(-50%, 0)",
1257
+ display: "flex",
1258
+ flexDirection: "column",
1259
+ gap: 10,
1260
+ width: "86%",
1261
+ maxWidth: 760,
1262
+ }}
1263
+ >
1264
+ {NEGATIVES.map((label, i) => {
1265
+ const delay = Math.round((0.4 + i * 0.55) * fps);
1266
+ const s = spring({
1267
+ frame: f,
1268
+ fps,
1269
+ delay,
1270
+ config: { damping: 13, stiffness: 110 },
1271
+ });
1272
+ const x = interpolate(s, [0, 1], [60, 0]);
1273
+ const itemOp = interpolate(s, [0, 0.3], [0, 1], {
1274
+ extrapolateLeft: "clamp",
1275
+ extrapolateRight: "clamp",
1276
+ });
1277
+
1278
+ // Strike line animates AFTER card lands
1279
+ const strikeDelay = delay + Math.round(0.3 * fps);
1280
+ const strikeWidth = interpolate(
1281
+ f,
1282
+ [strikeDelay, strikeDelay + 0.45 * fps],
1283
+ [0, 100],
1284
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" },
1285
+ );
1286
+
1287
+ return (
1288
+ <div
1289
+ key={label}
1290
+ style={{
1291
+ ...ds.card3d.cardStyle,
1292
+ padding: "14px 22px",
1293
+ display: "flex",
1294
+ alignItems: "center",
1295
+ gap: 14,
1296
+ opacity: itemOp,
1297
+ transform: `translateX(${x}px)`,
1298
+ background: "linear-gradient(145deg, #2a1a18, #1f1210)",
1299
+ border: "1px solid rgba(192,52,43,0.25)",
1300
+ }}
1301
+ >
1302
+ <div
1303
+ style={{
1304
+ width: 32,
1305
+ height: 32,
1306
+ borderRadius: 9,
1307
+ background: "rgba(192,52,43,0.25)",
1308
+ border: "1px solid rgba(192,52,43,0.5)",
1309
+ display: "flex",
1310
+ alignItems: "center",
1311
+ justifyContent: "center",
1312
+ flexShrink: 0,
1313
+ }}
1314
+ >
1315
+ <Icon.Cross color="#ff8b80" size={16} />
1316
+ </div>
1317
+ <div
1318
+ style={{
1319
+ flex: 1,
1320
+ position: "relative",
1321
+ fontFamily: FONT,
1322
+ fontSize: 24,
1323
+ fontWeight: ds.text.semibold,
1324
+ color: "rgba(255,200,195,0.85)",
1325
+ letterSpacing: -0.2,
1326
+ }}
1327
+ >
1328
+ no {label}
1329
+ <div
1330
+ style={{
1331
+ position: "absolute",
1332
+ top: "52%",
1333
+ left: 0,
1334
+ width: `${strikeWidth}%`,
1335
+ height: 2,
1336
+ background: "rgba(255,140,130,0.7)",
1337
+ borderRadius: 1,
1338
+ }}
1339
+ />
1340
+ </div>
1341
+ </div>
1342
+ );
1343
+ })}
1344
+ </div>
1345
+ </AbsoluteFill>
1346
+ );
1347
+ };
1348
+
1349
+ // ═══════════════════════════════════════════════════════════════
1350
+ // SCENE 7: ONE SKILL — clip is GONE; full motion-graphics climax
1351
+ // "Just one skill doing the whole pipeline"
1352
+ // ═══════════════════════════════════════════════════════════════
1353
+
1354
+ const OneSkillScene: React.FC<SP> = ({ frame, fps }) => {
1355
+ const op = so(frame, fps, SEGS[6].s, SEGS[6].e, 0.35, 0.4);
1356
+ if (op === 0) return null;
1357
+ const f = lf(frame, fps, SEGS[6].s);
1358
+
1359
+ // Hero icon entrance — gsap "back.out(1.6)" feel
1360
+ const heroSpring = spring({
1361
+ frame: f,
1362
+ fps,
1363
+ delay: Math.round(0.1 * fps),
1364
+ config: { damping: 9, stiffness: 90 },
1365
+ });
1366
+ const heroScale = interpolate(heroSpring, [0, 1], [0.2, 1]);
1367
+ const heroFloat = Math.sin((f / fps) * 1.6) * 8;
1368
+ const heroRot = interpolate(f, [0, SEGS[6].e * fps], [-30, 30]);
1369
+
1370
+ // Orbiting rings rotate
1371
+ const ringRot = (f / fps) * 50;
1372
+
1373
+ // Headline pop
1374
+ const titleSpring = spring({
1375
+ frame: f,
1376
+ fps,
1377
+ delay: Math.round(0.6 * fps),
1378
+ config: ds.spring.gentle,
1379
+ });
1380
+
1381
+ // 4 chips orbiting around the icon (assets, code, scene, ship)
1382
+ const chipsSpring = spring({
1383
+ frame: f,
1384
+ fps,
1385
+ delay: Math.round(1.4 * fps),
1386
+ config: { damping: 14, stiffness: 100 },
1387
+ });
1388
+
1389
+ // Tagline strap
1390
+ const strapOp = interpolate(f, [2.4 * fps, 3.0 * fps], [0, 1], {
1391
+ extrapolateLeft: "clamp",
1392
+ extrapolateRight: "clamp",
1393
+ });
1394
+ const strapPulse = 1 + Math.sin((f / fps) * 2.8) * 0.025;
1395
+
1396
+ return (
1397
+ <AbsoluteFill style={{ opacity: op, pointerEvents: "none" }}>
1398
+ {/* Orbital rings around hero */}
1399
+ <div
1400
+ style={{
1401
+ position: "absolute",
1402
+ top: "32%",
1403
+ left: "50%",
1404
+ transform: "translate(-50%, 0)",
1405
+ opacity: interpolate(heroSpring, [0, 1], [0, 0.7]),
1406
+ }}
1407
+ >
1408
+ <div style={{ position: "absolute", top: -150, left: -210 }}>
1409
+ <OrbitRing size={420} rotation={ringRot} tilt={70} />
1410
+ </div>
1411
+ <div style={{ position: "absolute", top: -110, left: -170 }}>
1412
+ <OrbitRing
1413
+ size={340}
1414
+ rotation={-ringRot * 0.7}
1415
+ tilt={65}
1416
+ color="rgba(30,60,40,0.4)"
1417
+ />
1418
+ </div>
1419
+ </div>
1420
+
1421
+ {/* Hero skill icon */}
1422
+ <div
1423
+ style={{
1424
+ position: "absolute",
1425
+ top: "26%",
1426
+ left: "50%",
1427
+ transform: `translate(-50%, ${heroFloat}px) scale(${heroScale})`,
1428
+ opacity: interpolate(heroSpring, [0, 0.2], [0, 1], {
1429
+ extrapolateLeft: "clamp",
1430
+ extrapolateRight: "clamp",
1431
+ }),
1432
+ }}
1433
+ >
1434
+ <SkillIcon size={220} rotation={heroRot} glow={0.55} />
1435
+ </div>
1436
+
1437
+ {/* Headline */}
1438
+ <div
1439
+ style={{
1440
+ position: "absolute",
1441
+ top: "52%",
1442
+ width: "100%",
1443
+ textAlign: "center",
1444
+ padding: "0 56px",
1445
+ transform: `scale(${interpolate(titleSpring, [0, 1], [0.85, 1])})`,
1446
+ opacity: interpolate(titleSpring, [0, 0.2], [0, 1], {
1447
+ extrapolateLeft: "clamp",
1448
+ extrapolateRight: "clamp",
1449
+ }),
1450
+ }}
1451
+ >
1452
+ <div
1453
+ style={{
1454
+ fontFamily: FONT,
1455
+ fontSize: 60,
1456
+ fontWeight: ds.text.bold,
1457
+ color: T.heading,
1458
+ lineHeight: 1.04,
1459
+ letterSpacing: ds.text.tighter,
1460
+ }}
1461
+ >
1462
+ One <span style={{ color: ds.color.claude }}>skill</span>.
1463
+ <br />
1464
+ Whole <span style={{ color: ds.color.claude }}>pipeline</span>.
1465
+ </div>
1466
+ </div>
1467
+
1468
+ {/* Pipeline chips floating in a row */}
1469
+ <div
1470
+ style={{
1471
+ position: "absolute",
1472
+ top: "67%",
1473
+ left: "50%",
1474
+ transform: `translate(-50%, ${interpolate(chipsSpring, [0, 1], [30, 0])}px)`,
1475
+ opacity: interpolate(chipsSpring, [0, 0.3], [0, 1], {
1476
+ extrapolateLeft: "clamp",
1477
+ extrapolateRight: "clamp",
1478
+ }),
1479
+ display: "flex",
1480
+ gap: 8,
1481
+ flexWrap: "wrap",
1482
+ justifyContent: "center",
1483
+ maxWidth: 920,
1484
+ }}
1485
+ >
1486
+ {PIPELINE.map((p, i) => {
1487
+ const delay = Math.round((1.4 + i * 0.18) * fps);
1488
+ const s = spring({
1489
+ frame: f,
1490
+ fps,
1491
+ delay,
1492
+ config: { damping: 13, stiffness: 110 },
1493
+ });
1494
+ const IconComp = Icon[p.icon];
1495
+ return (
1496
+ <div
1497
+ key={p.label}
1498
+ style={{
1499
+ background: "rgba(0,0,0,0.85)",
1500
+ border: "1px solid rgba(212,102,58,0.35)",
1501
+ borderRadius: ds.radius.pill,
1502
+ padding: "10px 18px",
1503
+ display: "flex",
1504
+ alignItems: "center",
1505
+ gap: 8,
1506
+ opacity: interpolate(s, [0, 0.3], [0, 1], {
1507
+ extrapolateLeft: "clamp",
1508
+ extrapolateRight: "clamp",
1509
+ }),
1510
+ transform: `scale(${interpolate(s, [0, 1], [0.4, 1])})`,
1511
+ }}
1512
+ >
1513
+ <IconComp color={ds.color.claude} size={16} />
1514
+ <span
1515
+ style={{
1516
+ fontFamily: MONO,
1517
+ fontSize: 13,
1518
+ fontWeight: ds.text.semibold,
1519
+ color: "#fff",
1520
+ letterSpacing: 0.6,
1521
+ textTransform: "uppercase",
1522
+ }}
1523
+ >
1524
+ {p.label}
1525
+ </span>
1526
+ </div>
1527
+ );
1528
+ })}
1529
+ </div>
1530
+
1531
+ {/* Bottom tagline */}
1532
+ <div
1533
+ style={{
1534
+ position: "absolute",
1535
+ bottom: "25%",
1536
+ width: "100%",
1537
+ textAlign: "center",
1538
+ opacity: strapOp,
1539
+ transform: `scale(${strapPulse})`,
1540
+ }}
1541
+ >
1542
+ <div
1543
+ style={{
1544
+ fontFamily: FONT,
1545
+ fontSize: 28,
1546
+ fontWeight: ds.text.semibold,
1547
+ color: T.heading,
1548
+ fontStyle: "italic",
1549
+ }}
1550
+ >
1551
+ asset → code → scene →{" "}
1552
+ <span style={{ color: T.success, fontStyle: "normal", fontWeight: ds.text.bold }}>
1553
+ shipped
1554
+ </span>
1555
+ </div>
1556
+ </div>
1557
+ </AbsoluteFill>
1558
+ );
1559
+ };
1560
+
1561
+ // ═══════════════════════════════════════════════════════════════
1562
+ // SCENE 8: CTA — "Comment AI down below..."
1563
+ // ═══════════════════════════════════════════════════════════════
1564
+
1565
+ const CTAScene: React.FC<SP> = ({ frame, fps }) => {
1566
+ const op = so(frame, fps, SEGS[7].s, SEGS[7].e, 0.4, 0.5);
1567
+ if (op === 0) return null;
1568
+ const f = lf(frame, fps, SEGS[7].s);
1569
+
1570
+ const mainSpring = spring({
1571
+ frame: f,
1572
+ fps,
1573
+ delay: Math.round(0.15 * fps),
1574
+ config: { damping: 10, stiffness: 90 },
1575
+ });
1576
+
1577
+ const bubbleSpring = spring({
1578
+ frame: f,
1579
+ fps,
1580
+ delay: Math.round(0.9 * fps),
1581
+ config: { damping: 11, stiffness: 100 },
1582
+ });
1583
+
1584
+ const arrowY = Math.sin((f / fps) * 3.5) * 10;
1585
+ const dropFloat = Math.sin((f / fps) * 2) * 6;
1586
+ const dropRot = interpolate(f, [0, 5 * fps], [0, 30]);
1587
+
1588
+ return (
1589
+ <AbsoluteFill style={{ opacity: op, pointerEvents: "none" }}>
1590
+ {/* Drop watermark */}
1591
+ <div
1592
+ style={{
1593
+ position: "absolute",
1594
+ top: "13%",
1595
+ left: "50%",
1596
+ transform: `translate(-50%, ${dropFloat}px) scale(${interpolate(mainSpring, [0, 1], [0.5, 1])})`,
1597
+ opacity: interpolate(mainSpring, [0, 0.3], [0, 1], {
1598
+ extrapolateLeft: "clamp",
1599
+ extrapolateRight: "clamp",
1600
+ }),
1601
+ }}
1602
+ >
1603
+ <SkillIcon size={100} rotation={dropRot} glow={0.45} />
1604
+ </div>
1605
+
1606
+ {/* Main instruction */}
1607
+ <div
1608
+ style={{
1609
+ position: "absolute",
1610
+ top: "29%",
1611
+ width: "100%",
1612
+ textAlign: "center",
1613
+ padding: "0 44px",
1614
+ transform: `scale(${interpolate(mainSpring, [0, 1], [0.88, 1])})`,
1615
+ opacity: interpolate(mainSpring, [0, 0.2], [0, 1], {
1616
+ extrapolateLeft: "clamp",
1617
+ extrapolateRight: "clamp",
1618
+ }),
1619
+ }}
1620
+ >
1621
+ <div
1622
+ style={{
1623
+ fontFamily: FONT,
1624
+ fontSize: 56,
1625
+ fontWeight: ds.text.bold,
1626
+ color: T.heading,
1627
+ letterSpacing: ds.text.tighter,
1628
+ lineHeight: 1.06,
1629
+ }}
1630
+ >
1631
+ Comment{" "}
1632
+ <span
1633
+ style={{
1634
+ color: ds.color.claude,
1635
+ textShadow: "0 0 30px rgba(212,102,58,0.3)",
1636
+ }}
1637
+ >
1638
+ "AI"
1639
+ </span>
1640
+ <br />
1641
+ for the full setup
1642
+ </div>
1643
+ </div>
1644
+
1645
+ {/* Comment bubble */}
1646
+ <div
1647
+ style={{
1648
+ position: "absolute",
1649
+ top: "52%",
1650
+ left: "50%",
1651
+ transform: `translate(-50%, 0) scale(${interpolate(bubbleSpring, [0, 1], [0.6, 1])})`,
1652
+ opacity: interpolate(bubbleSpring, [0, 0.3], [0, 1], {
1653
+ extrapolateLeft: "clamp",
1654
+ extrapolateRight: "clamp",
1655
+ }),
1656
+ width: "82%",
1657
+ maxWidth: 760,
1658
+ perspective: 1000,
1659
+ }}
1660
+ >
1661
+ <div
1662
+ style={{
1663
+ ...ds.card3d.cardStyle,
1664
+ padding: "20px 24px",
1665
+ transform: "rotateX(4deg) rotateY(-2deg)",
1666
+ transformStyle: "preserve-3d",
1667
+ display: "flex",
1668
+ alignItems: "center",
1669
+ gap: 16,
1670
+ }}
1671
+ >
1672
+ <div
1673
+ style={{
1674
+ width: 52,
1675
+ height: 52,
1676
+ borderRadius: "50%",
1677
+ background: "linear-gradient(135deg, #D4663A, #8B3A1E)",
1678
+ display: "flex",
1679
+ alignItems: "center",
1680
+ justifyContent: "center",
1681
+ flexShrink: 0,
1682
+ }}
1683
+ >
1684
+ <Icon.Chat color="#fff" size={24} />
1685
+ </div>
1686
+ <div style={{ flex: 1 }}>
1687
+ <div
1688
+ style={{
1689
+ fontFamily: FONT,
1690
+ fontSize: 13,
1691
+ color: G.textMuted,
1692
+ letterSpacing: 0.5,
1693
+ }}
1694
+ >
1695
+ you · just now
1696
+ </div>
1697
+ <div
1698
+ style={{
1699
+ fontFamily: FONT,
1700
+ fontSize: 30,
1701
+ fontWeight: ds.text.bold,
1702
+ color: G.text,
1703
+ marginTop: 2,
1704
+ letterSpacing: -0.3,
1705
+ }}
1706
+ >
1707
+ AI
1708
+ {f > 2 * fps && (
1709
+ <span
1710
+ style={{
1711
+ opacity: Math.sin(f * 0.3) > 0 ? 1 : 0,
1712
+ color: ds.color.claude,
1713
+ marginLeft: 2,
1714
+ }}
1715
+ >
1716
+ |
1717
+ </span>
1718
+ )}
1719
+ </div>
1720
+ </div>
1721
+ <div
1722
+ style={{
1723
+ width: 44,
1724
+ height: 44,
1725
+ borderRadius: 12,
1726
+ background: "rgba(212,102,58,0.22)",
1727
+ border: "1px solid rgba(212,102,58,0.45)",
1728
+ display: "flex",
1729
+ alignItems: "center",
1730
+ justifyContent: "center",
1731
+ flexShrink: 0,
1732
+ }}
1733
+ >
1734
+ <svg width={20} height={20} viewBox="0 0 24 24" fill="none">
1735
+ <path d="M3 12l18-8-5 18-4-8-9-2z" stroke={ds.color.claude} strokeWidth="1.8" strokeLinejoin="round" />
1736
+ </svg>
1737
+ </div>
1738
+ </div>
1739
+ </div>
1740
+
1741
+ {/* Bottom hint + arrow */}
1742
+ <div
1743
+ style={{
1744
+ position: "absolute",
1745
+ bottom: "26%",
1746
+ width: "100%",
1747
+ textAlign: "center",
1748
+ opacity: interpolate(f, [1.8 * fps, 2.4 * fps], [0, 1], {
1749
+ extrapolateLeft: "clamp",
1750
+ extrapolateRight: "clamp",
1751
+ }),
1752
+ }}
1753
+ >
1754
+ <div style={{ display: "inline-flex", flexDirection: "column", alignItems: "center", gap: 8 }}>
1755
+ <div
1756
+ style={{
1757
+ fontFamily: FONT,
1758
+ fontSize: 18,
1759
+ fontWeight: ds.text.medium,
1760
+ color: T.muted,
1761
+ letterSpacing: 1.5,
1762
+ textTransform: "uppercase",
1763
+ }}
1764
+ >
1765
+ running tonight · guaranteed
1766
+ </div>
1767
+ <div style={{ transform: `translateY(${arrowY}px)`, color: ds.color.claude }}>
1768
+ <Icon.ArrowDown color={ds.color.claude} size={32} />
1769
+ </div>
1770
+ </div>
1771
+ </div>
1772
+ </AbsoluteFill>
1773
+ );
1774
+ };
1775
+
1776
+ // ═══════════════════════════════════════════════════════════════
1777
+ // MAIN COMPOSITION
1778
+ // ═══════════════════════════════════════════════════════════════
1779
+
1780
+ export const Devini3DReel: React.FC = () => {
1781
+ const frame = useCurrentFrame();
1782
+ const { fps } = useVideoConfig();
1783
+
1784
+ return (
1785
+ <AbsoluteFill style={{ backgroundColor: "#e8e4de", fontFamily: FONT }}>
1786
+ {/* REFERENCE-STRIP: voiceover tag removed — bring your own voiceover */}
1787
+ <Background frame={frame} />
1788
+ <ClipShowcase frame={frame} fps={fps} />
1789
+ <HookScene frame={frame} fps={fps} />
1790
+ <WildPartScene frame={frame} fps={fps} />
1791
+ <BreadthScene frame={frame} fps={fps} />
1792
+ <PromptScene frame={frame} fps={fps} />
1793
+ <PipelineScene frame={frame} fps={fps} />
1794
+ <NoStitchingScene frame={frame} fps={fps} />
1795
+ <OneSkillScene frame={frame} fps={fps} />
1796
+ <CTAScene frame={frame} fps={fps} />
1797
+ </AbsoluteFill>
1798
+ );
1799
+ };