@devinilabs/reelstack 1.3.2 → 1.4.1
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.
- package/cli/scaffold.js +124 -16
- package/docs/buyers-guide.md +1 -1
- package/docs/family-galleries/dark.md +1 -1
- package/docs/family-galleries/forbidden.md +1 -1
- package/docs/family-galleries/glass.md +1 -1
- package/docs/family-galleries/paper.md +1 -1
- package/docs/family-galleries/warm.md +1 -1
- package/package.json +1 -1
- package/reference/dark/codedrop.tsx +908 -0
- package/reference/dark/gpt55.tsx +2608 -0
- package/reference/dark/resourcescta.tsx +609 -0
- package/reference/dark/skills.tsx +1460 -0
- package/reference/dark/stitch2.tsx +162 -0
- package/reference/glass/claudewatchcta.tsx +599 -0
- package/reference/glass/gstack.tsx +3020 -0
- package/reference/glass/jcode.tsx +2267 -0
- package/reference/glass/lilagents.tsx +2649 -0
- package/reference/paper/devini3d.tsx +1799 -0
- package/skill/SKILL.md +25 -16
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REFERENCE — ClaudeWatchCTAReel (Family: glass)
|
|
3
|
+
*
|
|
4
|
+
* Canonical example of the glass family's motion vocabulary, frame-locked
|
|
5
|
+
* BEAT structure, and scene choreography. Bundled with ReelStack v1.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/ClaudeWatchCTAReel.tsx
|
|
15
|
+
* Bundled at: 2026-05-12T19:40:04.826Z
|
|
16
|
+
*/
|
|
17
|
+
import React from "react";
|
|
18
|
+
import {
|
|
19
|
+
AbsoluteFill,
|
|
20
|
+
interpolate,
|
|
21
|
+
spring,
|
|
22
|
+
useCurrentFrame,
|
|
23
|
+
} from "remotion";
|
|
24
|
+
import {
|
|
25
|
+
C,
|
|
26
|
+
CausticBlobs,
|
|
27
|
+
EyebrowPill,
|
|
28
|
+
FONT,
|
|
29
|
+
FloatingGlyphs,
|
|
30
|
+
GLASS_SHADOW,
|
|
31
|
+
LightBeam,
|
|
32
|
+
MONO,
|
|
33
|
+
ParticleBurst,
|
|
34
|
+
SonarRings,
|
|
35
|
+
ease,
|
|
36
|
+
glassBase,
|
|
37
|
+
} from "./ClaudeWatchReel";
|
|
38
|
+
|
|
39
|
+
// 240 frames @ 30fps = 8s
|
|
40
|
+
export const CWCTA_TOTAL = 240;
|
|
41
|
+
|
|
42
|
+
const SAFE_TOP = 290;
|
|
43
|
+
const SAFE_BOTTOM = 410;
|
|
44
|
+
|
|
45
|
+
const BEATS = {
|
|
46
|
+
b1: 0, // 0.0s — atmosphere & eyebrow
|
|
47
|
+
b2: 30, // 1.0s — hero word stagger
|
|
48
|
+
b3: 105, // 3.5s — sub-line + glass play disc
|
|
49
|
+
b4: 165, // 5.5s — description pill
|
|
50
|
+
b5: 225, // 7.5s — "Start building." kicker
|
|
51
|
+
} as const;
|
|
52
|
+
|
|
53
|
+
// ───────────────────────────────────────────────────────────
|
|
54
|
+
// Frame-based tween (matches the pattern used in ClaudeWatchReel)
|
|
55
|
+
// ───────────────────────────────────────────────────────────
|
|
56
|
+
const tween = (
|
|
57
|
+
frame: number,
|
|
58
|
+
startFrame: number,
|
|
59
|
+
duration: number,
|
|
60
|
+
from: number,
|
|
61
|
+
to: number,
|
|
62
|
+
easeFn: (t: number) => number = ease.expoOut,
|
|
63
|
+
) =>
|
|
64
|
+
interpolate(frame, [startFrame, startFrame + duration], [from, to], {
|
|
65
|
+
extrapolateLeft: "clamp",
|
|
66
|
+
extrapolateRight: "clamp",
|
|
67
|
+
easing: easeFn,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// ───────────────────────────────────────────────────────────
|
|
71
|
+
// HERO LINE — single line with per-word stagger (fade-up + blur)
|
|
72
|
+
// Variant: optional emphasis word recolored & scaled-overshoot.
|
|
73
|
+
// ───────────────────────────────────────────────────────────
|
|
74
|
+
const HeroLine: React.FC<{
|
|
75
|
+
text: string;
|
|
76
|
+
startFrame: number;
|
|
77
|
+
fontSize: number;
|
|
78
|
+
fontWeight?: number;
|
|
79
|
+
color?: string;
|
|
80
|
+
letterSpacing?: string;
|
|
81
|
+
italic?: boolean;
|
|
82
|
+
perWordDelay?: number;
|
|
83
|
+
duration?: number;
|
|
84
|
+
emphasisOvershoot?: boolean;
|
|
85
|
+
}> = ({
|
|
86
|
+
text,
|
|
87
|
+
startFrame,
|
|
88
|
+
fontSize,
|
|
89
|
+
fontWeight = 800,
|
|
90
|
+
color = C.ink,
|
|
91
|
+
letterSpacing = "-0.04em",
|
|
92
|
+
italic = false,
|
|
93
|
+
perWordDelay = 5,
|
|
94
|
+
duration = 22,
|
|
95
|
+
emphasisOvershoot = false,
|
|
96
|
+
}) => {
|
|
97
|
+
const frame = useCurrentFrame();
|
|
98
|
+
const words = text.split(" ");
|
|
99
|
+
return (
|
|
100
|
+
<div
|
|
101
|
+
style={{
|
|
102
|
+
display: "flex",
|
|
103
|
+
flexWrap: "nowrap",
|
|
104
|
+
gap: "0.28em",
|
|
105
|
+
justifyContent: "center",
|
|
106
|
+
fontFamily: FONT,
|
|
107
|
+
fontSize,
|
|
108
|
+
fontWeight,
|
|
109
|
+
color,
|
|
110
|
+
letterSpacing,
|
|
111
|
+
lineHeight: 1.02,
|
|
112
|
+
fontStyle: italic ? "italic" : "normal",
|
|
113
|
+
whiteSpace: "nowrap",
|
|
114
|
+
}}
|
|
115
|
+
>
|
|
116
|
+
{words.map((w, i) => {
|
|
117
|
+
const wordStart = startFrame + i * perWordDelay;
|
|
118
|
+
const local = frame - wordStart;
|
|
119
|
+
const op = interpolate(local, [0, duration], [0, 1], {
|
|
120
|
+
extrapolateLeft: "clamp",
|
|
121
|
+
extrapolateRight: "clamp",
|
|
122
|
+
easing: ease.expoOut,
|
|
123
|
+
});
|
|
124
|
+
const y = interpolate(local, [0, duration], [38, 0], {
|
|
125
|
+
extrapolateLeft: "clamp",
|
|
126
|
+
extrapolateRight: "clamp",
|
|
127
|
+
easing: ease.power3Out,
|
|
128
|
+
});
|
|
129
|
+
const blur = interpolate(local, [0, duration], [10, 0], {
|
|
130
|
+
extrapolateLeft: "clamp",
|
|
131
|
+
extrapolateRight: "clamp",
|
|
132
|
+
});
|
|
133
|
+
// Emphasis overshoot bumps the LAST word past 1.0 then settles
|
|
134
|
+
const isLast = i === words.length - 1;
|
|
135
|
+
const baseScale = interpolate(local, [0, duration], [0.92, 1], {
|
|
136
|
+
extrapolateLeft: "clamp",
|
|
137
|
+
extrapolateRight: "clamp",
|
|
138
|
+
easing: ease.expoOut,
|
|
139
|
+
});
|
|
140
|
+
const overshoot = emphasisOvershoot && isLast
|
|
141
|
+
? interpolate(local, [duration, duration + 8, duration + 18], [1, 1.06, 1], {
|
|
142
|
+
extrapolateLeft: "clamp",
|
|
143
|
+
extrapolateRight: "clamp",
|
|
144
|
+
easing: ease.power3Out,
|
|
145
|
+
})
|
|
146
|
+
: 1;
|
|
147
|
+
return (
|
|
148
|
+
<span
|
|
149
|
+
key={i}
|
|
150
|
+
style={{
|
|
151
|
+
display: "inline-block",
|
|
152
|
+
opacity: op,
|
|
153
|
+
transform: `translateY(${y}px) scale(${baseScale * overshoot})`,
|
|
154
|
+
filter: `blur(${blur}px)`,
|
|
155
|
+
willChange: "transform, opacity, filter",
|
|
156
|
+
}}
|
|
157
|
+
>
|
|
158
|
+
{w}
|
|
159
|
+
</span>
|
|
160
|
+
);
|
|
161
|
+
})}
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// ───────────────────────────────────────────────────────────
|
|
167
|
+
// GLASS PLAY DISC — 240px disc with ▶ triangle + dual sonar
|
|
168
|
+
// ───────────────────────────────────────────────────────────
|
|
169
|
+
const GlassPlayDisc: React.FC<{ startFrame: number }> = ({ startFrame }) => {
|
|
170
|
+
const frame = useCurrentFrame();
|
|
171
|
+
const local = frame - startFrame;
|
|
172
|
+
|
|
173
|
+
// Spring entrance with overshoot
|
|
174
|
+
const entrance = spring({
|
|
175
|
+
frame: local,
|
|
176
|
+
fps: 30,
|
|
177
|
+
config: { damping: 11, stiffness: 130 },
|
|
178
|
+
from: 0,
|
|
179
|
+
to: 1,
|
|
180
|
+
});
|
|
181
|
+
const op = interpolate(local, [0, 18], [0, 1], {
|
|
182
|
+
extrapolateLeft: "clamp",
|
|
183
|
+
extrapolateRight: "clamp",
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Subtle scale-breathe (±2% at ~0.5 Hz)
|
|
187
|
+
const breathe = 1 + Math.sin(frame * 0.06) * 0.02;
|
|
188
|
+
// Outro kick at B5 — extra 4% pop on the last 15 frames
|
|
189
|
+
const kickStart = BEATS.b5;
|
|
190
|
+
const kick = interpolate(frame, [kickStart, kickStart + 8, kickStart + 15], [1, 1.04, 1], {
|
|
191
|
+
extrapolateLeft: "clamp",
|
|
192
|
+
extrapolateRight: "clamp",
|
|
193
|
+
easing: ease.expoOut,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const size = 240;
|
|
197
|
+
return (
|
|
198
|
+
<div
|
|
199
|
+
style={{
|
|
200
|
+
position: "absolute",
|
|
201
|
+
top: 820,
|
|
202
|
+
left: "50%",
|
|
203
|
+
width: size,
|
|
204
|
+
height: size,
|
|
205
|
+
marginLeft: -size / 2,
|
|
206
|
+
opacity: op,
|
|
207
|
+
transform: `scale(${entrance * breathe * kick})`,
|
|
208
|
+
willChange: "transform, opacity",
|
|
209
|
+
}}
|
|
210
|
+
>
|
|
211
|
+
{/* Sonar layer 1 — claude coral */}
|
|
212
|
+
<div
|
|
213
|
+
style={{
|
|
214
|
+
position: "absolute",
|
|
215
|
+
inset: 0,
|
|
216
|
+
pointerEvents: "none",
|
|
217
|
+
}}
|
|
218
|
+
>
|
|
219
|
+
<DiscSonar startFrame={startFrame + 6} accent={C.claude} period={70} />
|
|
220
|
+
</div>
|
|
221
|
+
{/* Sonar layer 2 — vector indigo, offset phase */}
|
|
222
|
+
<div
|
|
223
|
+
style={{
|
|
224
|
+
position: "absolute",
|
|
225
|
+
inset: 0,
|
|
226
|
+
pointerEvents: "none",
|
|
227
|
+
}}
|
|
228
|
+
>
|
|
229
|
+
<DiscSonar startFrame={startFrame + 30} accent={C.vector} period={70} />
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
{/* The disc itself */}
|
|
233
|
+
<div
|
|
234
|
+
style={{
|
|
235
|
+
position: "absolute",
|
|
236
|
+
inset: 0,
|
|
237
|
+
borderRadius: "50%",
|
|
238
|
+
background: C.glassFillStrong,
|
|
239
|
+
backdropFilter: "blur(32px) saturate(180%)",
|
|
240
|
+
WebkitBackdropFilter: "blur(32px) saturate(180%)",
|
|
241
|
+
border: `1.5px solid ${C.glassBorder}`,
|
|
242
|
+
boxShadow: GLASS_SHADOW,
|
|
243
|
+
display: "flex",
|
|
244
|
+
alignItems: "center",
|
|
245
|
+
justifyContent: "center",
|
|
246
|
+
}}
|
|
247
|
+
>
|
|
248
|
+
{/* ▶ triangle — CSS border, optically nudged right */}
|
|
249
|
+
<div
|
|
250
|
+
style={{
|
|
251
|
+
width: 0,
|
|
252
|
+
height: 0,
|
|
253
|
+
borderLeft: `66px solid ${C.claude}`,
|
|
254
|
+
borderTop: "44px solid transparent",
|
|
255
|
+
borderBottom: "44px solid transparent",
|
|
256
|
+
marginLeft: 16,
|
|
257
|
+
filter: `drop-shadow(0 6px 16px ${C.claude}55)`,
|
|
258
|
+
}}
|
|
259
|
+
/>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// Sonar pulse anchored to the disc center (not the canvas center)
|
|
266
|
+
const DiscSonar: React.FC<{ startFrame: number; accent: string; period: number }> = ({
|
|
267
|
+
startFrame,
|
|
268
|
+
accent,
|
|
269
|
+
period,
|
|
270
|
+
}) => {
|
|
271
|
+
const frame = useCurrentFrame();
|
|
272
|
+
const local = frame - startFrame;
|
|
273
|
+
if (local < 0) return null;
|
|
274
|
+
const rings = [0, period * 0.33, period * 0.66];
|
|
275
|
+
return (
|
|
276
|
+
<>
|
|
277
|
+
{rings.map((birth, i) => {
|
|
278
|
+
const ringLocal = local - birth;
|
|
279
|
+
if (ringLocal < 0) return null;
|
|
280
|
+
const cycle = ringLocal % period;
|
|
281
|
+
const scale = interpolate(cycle, [0, period], [0.5, 1.9], {
|
|
282
|
+
extrapolateLeft: "clamp",
|
|
283
|
+
extrapolateRight: "clamp",
|
|
284
|
+
easing: ease.expoOut,
|
|
285
|
+
});
|
|
286
|
+
const op = interpolate(cycle, [0, period * 0.3, period], [0, 0.32, 0], {
|
|
287
|
+
extrapolateLeft: "clamp",
|
|
288
|
+
extrapolateRight: "clamp",
|
|
289
|
+
});
|
|
290
|
+
return (
|
|
291
|
+
<div
|
|
292
|
+
key={i}
|
|
293
|
+
style={{
|
|
294
|
+
position: "absolute",
|
|
295
|
+
inset: 0,
|
|
296
|
+
borderRadius: "50%",
|
|
297
|
+
border: `2px solid ${accent}`,
|
|
298
|
+
transform: `scale(${scale})`,
|
|
299
|
+
opacity: op,
|
|
300
|
+
willChange: "transform, opacity",
|
|
301
|
+
}}
|
|
302
|
+
/>
|
|
303
|
+
);
|
|
304
|
+
})}
|
|
305
|
+
</>
|
|
306
|
+
);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// ───────────────────────────────────────────────────────────
|
|
310
|
+
// DESCRIPTION PILL — bottom glass card with arrow caption above
|
|
311
|
+
// ───────────────────────────────────────────────────────────
|
|
312
|
+
const DescriptionPill: React.FC<{ startFrame: number }> = ({ startFrame }) => {
|
|
313
|
+
const frame = useCurrentFrame();
|
|
314
|
+
const local = frame - startFrame;
|
|
315
|
+
|
|
316
|
+
const entrance = spring({
|
|
317
|
+
frame: local,
|
|
318
|
+
fps: 30,
|
|
319
|
+
config: { damping: 14, stiffness: 140 },
|
|
320
|
+
from: 0,
|
|
321
|
+
to: 1,
|
|
322
|
+
});
|
|
323
|
+
const op = interpolate(local, [0, 15], [0, 1], {
|
|
324
|
+
extrapolateLeft: "clamp",
|
|
325
|
+
extrapolateRight: "clamp",
|
|
326
|
+
});
|
|
327
|
+
const y = interpolate(local, [0, 22], [40, 0], {
|
|
328
|
+
extrapolateLeft: "clamp",
|
|
329
|
+
extrapolateRight: "clamp",
|
|
330
|
+
easing: ease.power3Out,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Caption pulse (arrows nudge down rhythmically)
|
|
334
|
+
const arrowBob = Math.sin(frame * 0.15) * 6;
|
|
335
|
+
const captionOp = interpolate(local, [0, 18], [0, 1], {
|
|
336
|
+
extrapolateLeft: "clamp",
|
|
337
|
+
extrapolateRight: "clamp",
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
return (
|
|
341
|
+
<div
|
|
342
|
+
style={{
|
|
343
|
+
position: "absolute",
|
|
344
|
+
left: 60,
|
|
345
|
+
right: 60,
|
|
346
|
+
bottom: SAFE_BOTTOM + 20,
|
|
347
|
+
display: "flex",
|
|
348
|
+
flexDirection: "column",
|
|
349
|
+
alignItems: "center",
|
|
350
|
+
gap: 22,
|
|
351
|
+
transform: `translateY(${y}px) scale(${0.96 + entrance * 0.04})`,
|
|
352
|
+
opacity: op,
|
|
353
|
+
}}
|
|
354
|
+
>
|
|
355
|
+
{/* Mono caption with bobbing arrows */}
|
|
356
|
+
<div
|
|
357
|
+
style={{
|
|
358
|
+
fontFamily: MONO,
|
|
359
|
+
fontSize: 22,
|
|
360
|
+
fontWeight: 500,
|
|
361
|
+
letterSpacing: "0.22em",
|
|
362
|
+
textTransform: "uppercase",
|
|
363
|
+
color: C.inkSoft,
|
|
364
|
+
opacity: captionOp,
|
|
365
|
+
display: "flex",
|
|
366
|
+
alignItems: "center",
|
|
367
|
+
gap: 14,
|
|
368
|
+
}}
|
|
369
|
+
>
|
|
370
|
+
<span style={{ transform: `translateY(${arrowBob}px)`, color: C.claude }}>↓</span>
|
|
371
|
+
<span>Tap the description</span>
|
|
372
|
+
<span style={{ transform: `translateY(${arrowBob}px)`, color: C.claude }}>↓</span>
|
|
373
|
+
</div>
|
|
374
|
+
|
|
375
|
+
{/* Glass pill with the message */}
|
|
376
|
+
<div
|
|
377
|
+
style={{
|
|
378
|
+
...glassBase,
|
|
379
|
+
borderRadius: 32,
|
|
380
|
+
padding: "26px 44px",
|
|
381
|
+
textAlign: "center",
|
|
382
|
+
fontFamily: FONT,
|
|
383
|
+
fontSize: 38,
|
|
384
|
+
fontWeight: 600,
|
|
385
|
+
color: C.ink,
|
|
386
|
+
letterSpacing: "-0.01em",
|
|
387
|
+
lineHeight: 1.18,
|
|
388
|
+
maxWidth: 880,
|
|
389
|
+
}}
|
|
390
|
+
>
|
|
391
|
+
Complete toolkit in <span style={{ color: C.vector, fontWeight: 700 }}>the description</span>
|
|
392
|
+
</div>
|
|
393
|
+
</div>
|
|
394
|
+
);
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// ───────────────────────────────────────────────────────────
|
|
398
|
+
// VIGNETTE — slow inward darkening
|
|
399
|
+
// ───────────────────────────────────────────────────────────
|
|
400
|
+
const Vignette: React.FC = () => {
|
|
401
|
+
const frame = useCurrentFrame();
|
|
402
|
+
const op = interpolate(frame, [0, 60, BEATS.b5, CWCTA_TOTAL], [0.2, 0.32, 0.32, 0.5], {
|
|
403
|
+
extrapolateLeft: "clamp",
|
|
404
|
+
extrapolateRight: "clamp",
|
|
405
|
+
});
|
|
406
|
+
return (
|
|
407
|
+
<AbsoluteFill
|
|
408
|
+
style={{
|
|
409
|
+
background:
|
|
410
|
+
"radial-gradient(ellipse at 50% 50%, transparent 35%, rgba(20,15,28,1) 100%)",
|
|
411
|
+
opacity: op,
|
|
412
|
+
pointerEvents: "none",
|
|
413
|
+
}}
|
|
414
|
+
/>
|
|
415
|
+
);
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
// ───────────────────────────────────────────────────────────
|
|
419
|
+
// MAIN COMPONENT
|
|
420
|
+
// ───────────────────────────────────────────────────────────
|
|
421
|
+
export const ClaudeWatchCTAReel: React.FC = () => {
|
|
422
|
+
const frame = useCurrentFrame();
|
|
423
|
+
|
|
424
|
+
// ─── B2 hero word starts (3 stacked lines)
|
|
425
|
+
const heroStart = BEATS.b2;
|
|
426
|
+
const line1Start = heroStart; // "CATCH THE" (96px, 800)
|
|
427
|
+
const line2Start = heroStart + 18; // "FULL VIDEO" (132px, 800)
|
|
428
|
+
const line3Start = heroStart + 38; // "BELOW" (168px, 800 italic, claude)
|
|
429
|
+
|
|
430
|
+
// ─── B3 sub-line "Dive into this custom skill"
|
|
431
|
+
const subStart = BEATS.b3 + 14;
|
|
432
|
+
|
|
433
|
+
// ─── B5 "Start building." italic kicker
|
|
434
|
+
const kickerLocal = frame - BEATS.b5;
|
|
435
|
+
const kickerScale = spring({
|
|
436
|
+
frame: kickerLocal,
|
|
437
|
+
fps: 30,
|
|
438
|
+
config: { damping: 10, stiffness: 130 },
|
|
439
|
+
from: 0.6,
|
|
440
|
+
to: 1,
|
|
441
|
+
});
|
|
442
|
+
const kickerOp = interpolate(kickerLocal, [0, 14], [0, 1], {
|
|
443
|
+
extrapolateLeft: "clamp",
|
|
444
|
+
extrapolateRight: "clamp",
|
|
445
|
+
});
|
|
446
|
+
const kickerY = interpolate(kickerLocal, [0, 22], [22, 0], {
|
|
447
|
+
extrapolateLeft: "clamp",
|
|
448
|
+
extrapolateRight: "clamp",
|
|
449
|
+
easing: ease.power3Out,
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// ─── Eyebrow pill spring
|
|
453
|
+
const eyebrowSpring = spring({
|
|
454
|
+
frame: frame - 4,
|
|
455
|
+
fps: 30,
|
|
456
|
+
config: { damping: 14, stiffness: 140 },
|
|
457
|
+
from: 0,
|
|
458
|
+
to: 1,
|
|
459
|
+
});
|
|
460
|
+
const eyebrowOp = interpolate(frame, [0, 18], [0, 1], {
|
|
461
|
+
extrapolateLeft: "clamp",
|
|
462
|
+
extrapolateRight: "clamp",
|
|
463
|
+
});
|
|
464
|
+
const eyebrowY = interpolate(frame, [0, 24], [24, 0], {
|
|
465
|
+
extrapolateLeft: "clamp",
|
|
466
|
+
extrapolateRight: "clamp",
|
|
467
|
+
easing: ease.power3Out,
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
return (
|
|
471
|
+
<AbsoluteFill style={{ background: C.bg, overflow: "hidden" }}>
|
|
472
|
+
{/* Always-on atmosphere */}
|
|
473
|
+
<CausticBlobs />
|
|
474
|
+
<FloatingGlyphs />
|
|
475
|
+
|
|
476
|
+
{/* B2 emphasis — particle burst behind the headline */}
|
|
477
|
+
{frame >= BEATS.b2 + 40 && frame < BEATS.b2 + 130 && (
|
|
478
|
+
<ParticleBurst count={48} palette={[C.claude, C.film, C.vector, C.plasma]} />
|
|
479
|
+
)}
|
|
480
|
+
|
|
481
|
+
{/* B2 light beam sweep #1 (upper third) */}
|
|
482
|
+
{frame >= BEATS.b2 && frame < BEATS.b2 + 70 && (
|
|
483
|
+
<LightBeam delay={BEATS.b2 + 8} angle={-6} />
|
|
484
|
+
)}
|
|
485
|
+
|
|
486
|
+
{/* B4 light beam sweep #2 (opposite diagonal) */}
|
|
487
|
+
{frame >= BEATS.b4 && frame < BEATS.b4 + 70 && (
|
|
488
|
+
<LightBeam delay={BEATS.b4 + 6} angle={10} />
|
|
489
|
+
)}
|
|
490
|
+
|
|
491
|
+
{/* B4 emphasis — particle burst behind the description pill */}
|
|
492
|
+
{frame >= BEATS.b4 + 6 && frame < BEATS.b4 + 90 && (
|
|
493
|
+
<ParticleBurst count={36} palette={[C.vector, C.plasma, C.claude, C.film]} />
|
|
494
|
+
)}
|
|
495
|
+
|
|
496
|
+
{/* Background sonar around the whole canvas (subtle) */}
|
|
497
|
+
<div style={{ opacity: 0.55 }}>
|
|
498
|
+
<SonarRings accent={C.claude} secondary={C.vector} />
|
|
499
|
+
</div>
|
|
500
|
+
|
|
501
|
+
{/* Eyebrow pill — top */}
|
|
502
|
+
<div
|
|
503
|
+
style={{
|
|
504
|
+
position: "absolute",
|
|
505
|
+
top: SAFE_TOP - 50,
|
|
506
|
+
left: 0,
|
|
507
|
+
right: 0,
|
|
508
|
+
display: "flex",
|
|
509
|
+
justifyContent: "center",
|
|
510
|
+
opacity: eyebrowOp,
|
|
511
|
+
transform: `translateY(${eyebrowY - (1 - eyebrowSpring) * 6}px)`,
|
|
512
|
+
}}
|
|
513
|
+
>
|
|
514
|
+
<EyebrowPill dot={C.claude}>Watch full video</EyebrowPill>
|
|
515
|
+
</div>
|
|
516
|
+
|
|
517
|
+
{/* Hero block — 3 stacked lines, centered horizontally */}
|
|
518
|
+
<div
|
|
519
|
+
style={{
|
|
520
|
+
position: "absolute",
|
|
521
|
+
top: 400,
|
|
522
|
+
left: 0,
|
|
523
|
+
right: 0,
|
|
524
|
+
display: "flex",
|
|
525
|
+
flexDirection: "column",
|
|
526
|
+
alignItems: "center",
|
|
527
|
+
gap: 6,
|
|
528
|
+
}}
|
|
529
|
+
>
|
|
530
|
+
<HeroLine text="Catch the" startFrame={line1Start} fontSize={96} fontWeight={800} />
|
|
531
|
+
<HeroLine text="full video" startFrame={line2Start} fontSize={132} fontWeight={800} letterSpacing="-0.045em" />
|
|
532
|
+
<HeroLine
|
|
533
|
+
text="below"
|
|
534
|
+
startFrame={line3Start}
|
|
535
|
+
fontSize={168}
|
|
536
|
+
fontWeight={800}
|
|
537
|
+
color={C.claude}
|
|
538
|
+
italic
|
|
539
|
+
letterSpacing="-0.05em"
|
|
540
|
+
emphasisOvershoot
|
|
541
|
+
/>
|
|
542
|
+
</div>
|
|
543
|
+
|
|
544
|
+
{/* Glass play disc + dual sonar (B3 onwards) */}
|
|
545
|
+
{frame >= BEATS.b3 && <GlassPlayDisc startFrame={BEATS.b3} />}
|
|
546
|
+
|
|
547
|
+
{/* Sub-line below the disc */}
|
|
548
|
+
<div
|
|
549
|
+
style={{
|
|
550
|
+
position: "absolute",
|
|
551
|
+
top: 1100,
|
|
552
|
+
left: 0,
|
|
553
|
+
right: 0,
|
|
554
|
+
textAlign: "center",
|
|
555
|
+
}}
|
|
556
|
+
>
|
|
557
|
+
<HeroLine
|
|
558
|
+
text="Dive into this custom skill"
|
|
559
|
+
startFrame={subStart}
|
|
560
|
+
fontSize={42}
|
|
561
|
+
fontWeight={600}
|
|
562
|
+
letterSpacing="-0.01em"
|
|
563
|
+
color={C.inkSoft}
|
|
564
|
+
perWordDelay={3}
|
|
565
|
+
duration={18}
|
|
566
|
+
/>
|
|
567
|
+
</div>
|
|
568
|
+
|
|
569
|
+
{/* B5 "Start building." italic kicker */}
|
|
570
|
+
{frame >= BEATS.b5 && (
|
|
571
|
+
<div
|
|
572
|
+
style={{
|
|
573
|
+
position: "absolute",
|
|
574
|
+
top: 1230,
|
|
575
|
+
left: 0,
|
|
576
|
+
right: 0,
|
|
577
|
+
textAlign: "center",
|
|
578
|
+
fontFamily: FONT,
|
|
579
|
+
fontSize: 56,
|
|
580
|
+
fontWeight: 700,
|
|
581
|
+
fontStyle: "italic",
|
|
582
|
+
color: C.ink,
|
|
583
|
+
letterSpacing: "-0.02em",
|
|
584
|
+
opacity: kickerOp,
|
|
585
|
+
transform: `translateY(${kickerY}px) scale(${kickerScale})`,
|
|
586
|
+
}}
|
|
587
|
+
>
|
|
588
|
+
Start building.
|
|
589
|
+
</div>
|
|
590
|
+
)}
|
|
591
|
+
|
|
592
|
+
{/* B4 description pill (bottom) */}
|
|
593
|
+
{frame >= BEATS.b4 && <DescriptionPill startFrame={BEATS.b4} />}
|
|
594
|
+
|
|
595
|
+
{/* Slow inward vignette */}
|
|
596
|
+
<Vignette />
|
|
597
|
+
</AbsoluteFill>
|
|
598
|
+
);
|
|
599
|
+
};
|