@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.
@@ -0,0 +1,3020 @@
1
+ /**
2
+ * REFERENCE — GstackReel (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/GstackReel.tsx
15
+ * Bundled at: 2026-05-12T19:40:04.819Z
16
+ */
17
+ import React from "react";
18
+ import {
19
+ AbsoluteFill,
20
+ Easing,
21
+ Img,
22
+ OffthreadVideo,
23
+ Sequence,
24
+ interpolate,
25
+ spring,
26
+ staticFile,
27
+ useCurrentFrame,
28
+ useVideoConfig,
29
+ } from "remotion";
30
+ import { ds } from "./designSystem";
31
+
32
+ // ═══════════════════════════════════════════════════════════════
33
+ // TIMING — 94.0s @ 30fps = 2820 frames, audio-locked via Whisper transcript
34
+ // ═══════════════════════════════════════════════════════════════
35
+ export const GSTACK_TOTAL = 2820;
36
+
37
+ export const BEAT = {
38
+ hook: 0, // 0.0s — "The president of YC just open-sourced his entire Claude Code setup."
39
+ reveal: 165, // 5.5s — "The repo is called gstack."
40
+ pitch: 222, // 7.4s — "You install it once...23 specialists...each one with their own job."
41
+ officeHours: 542,// 18.08s — "Here's what blew my mind. /office-hours...AI pushes back."
42
+ reframe: 780, // 26.0s — "Like, you say...daily briefing app...chief of staff AI...reframes."
43
+ qa: 1065, // 35.5s — "Then there's /QA. Opens a real Chrome browser...by itself."
44
+ cso: 1418, // 47.28s — "/CSO runs a full OWASP security audit...for free."
45
+ parallel: 1754, // 58.48s — "And here's the unhinged part. 10 to 15 in parallel...same time."
46
+ garry: 2115, // 70.5s — "Built by Garry Tan...800× faster...same guy. Different tooling."
47
+ cta: 2580, // 86.0s — "It's free. MIT. 85,000 stars. Check the pinned comment."
48
+ end: 2820,
49
+ } as const;
50
+
51
+ // GSAP-equivalent eases mapped into Remotion
52
+ export const ease = {
53
+ power2Out: Easing.bezier(0.165, 0.84, 0.44, 1),
54
+ power3Out: Easing.bezier(0.215, 0.61, 0.355, 1),
55
+ expoOut: Easing.bezier(0.19, 1, 0.22, 1),
56
+ expoIn: Easing.bezier(0.7, 0, 0.84, 0),
57
+ backOut: Easing.bezier(0.34, 1.56, 0.64, 1),
58
+ inOut: Easing.bezier(0.45, 0, 0.55, 1),
59
+ };
60
+
61
+ // ═══════════════════════════════════════════════════════════════
62
+ // LIGHT IRIDESCENT GLASS PALETTE — agency-tier, desaturated
63
+ // ═══════════════════════════════════════════════════════════════
64
+ export const C = {
65
+ bg: "#EFEAF2",
66
+ bgWarm: "#E9E2EE",
67
+ bgCool: "#E4E9F2",
68
+ ink: "#0E0E12",
69
+ inkSoft: "#26242C",
70
+ inkMuted: "#5A5867",
71
+ inkDim: "#86848F",
72
+ hairline: "rgba(15,15,22,0.08)",
73
+ iriCyan: "#7FE8D4",
74
+ iriViolet: "#8B7FE8",
75
+ iriRose: "#E89BC4",
76
+ iriGold: "#F2D88F",
77
+ glassFill: "rgba(255,255,255,0.42)",
78
+ glassFillStrong: "rgba(255,255,255,0.62)",
79
+ glassBorder: "rgba(255,255,255,0.85)",
80
+ ycOrange: "#F26625",
81
+ emerald: "#34D399",
82
+ rose: "#FB7185",
83
+ } as const;
84
+
85
+ export const FONT = ds.font.sans;
86
+ export const MONO = ds.font.mono;
87
+
88
+ // Glass primitive
89
+ export const glassBase: React.CSSProperties = {
90
+ background: C.glassFill,
91
+ backdropFilter: "blur(32px) saturate(180%)",
92
+ WebkitBackdropFilter: "blur(32px) saturate(180%)",
93
+ border: `1.5px solid ${C.glassBorder}`,
94
+ boxShadow: [
95
+ "0 24px 48px -12px rgba(120,100,180,0.22)",
96
+ "0 8px 16px -4px rgba(120,100,180,0.12)",
97
+ "inset 0 1.5px 0 rgba(255,255,255,0.95)",
98
+ "inset 0 -1px 0 rgba(255,255,255,0.30)",
99
+ ].join(", "),
100
+ };
101
+
102
+ // Reels safe zones (1080×1920)
103
+ const SAFE_TOP = 290; // 15% top reserved for IG chrome
104
+ const SAFE_BOTTOM = 1500; // bottom 22% reserved
105
+
106
+ // ═══════════════════════════════════════════════════════════════
107
+ // GLOBAL ATMOSPHERE — caustic blobs, hairline grid, grain
108
+ // ═══════════════════════════════════════════════════════════════
109
+ export const CausticBlobs: React.FC = () => {
110
+ const frame = useCurrentFrame();
111
+ const t = frame / 30;
112
+ const blob = (
113
+ color: string,
114
+ size: number,
115
+ cx: number,
116
+ cy: number,
117
+ speedX: number,
118
+ speedY: number,
119
+ phase: number,
120
+ opacity = 0.55,
121
+ ): React.CSSProperties => ({
122
+ position: "absolute",
123
+ width: size,
124
+ height: size,
125
+ borderRadius: "50%",
126
+ background: `radial-gradient(circle at 50% 50%, ${color} 0%, ${color}99 30%, transparent 70%)`,
127
+ filter: "blur(120px)",
128
+ left: cx + Math.sin(t * speedX + phase) * 220,
129
+ top: cy + Math.cos(t * speedY + phase) * 160,
130
+ opacity,
131
+ pointerEvents: "none",
132
+ willChange: "transform",
133
+ });
134
+
135
+ return (
136
+ <AbsoluteFill style={{ overflow: "hidden" }}>
137
+ <div style={blob(C.iriCyan, 720, 80, 220, 0.18, 0.13, 0.0, 0.7)} />
138
+ <div style={blob(C.iriViolet, 820, 600, 540, 0.14, 0.16, 1.2, 0.65)} />
139
+ <div style={blob(C.iriRose, 700, 200, 1100, 0.11, 0.19, 2.4, 0.55)} />
140
+ <div style={blob(C.iriGold, 540, 720, 1500, 0.16, 0.12, 3.6, 0.4)} />
141
+ <div style={blob(C.iriCyan, 600, 100, 1700, 0.12, 0.18, 4.2, 0.4)} />
142
+ </AbsoluteFill>
143
+ );
144
+ };
145
+
146
+ export const HairlineGrid: React.FC<{ opacity?: number }> = ({ opacity = 0.05 }) => (
147
+ <AbsoluteFill
148
+ style={{
149
+ backgroundImage: `linear-gradient(${C.ink} 1px, transparent 1px), linear-gradient(90deg, ${C.ink} 1px, transparent 1px)`,
150
+ backgroundSize: "60px 60px",
151
+ opacity,
152
+ pointerEvents: "none",
153
+ }}
154
+ />
155
+ );
156
+
157
+ // ═══════════════════════════════════════════════════════════════
158
+ // PRIMITIVES — IridescentRing, GlassCard, EyebrowPill
159
+ // ═══════════════════════════════════════════════════════════════
160
+ export const IridescentRing: React.FC<{
161
+ size: number;
162
+ thickness?: number;
163
+ speed?: number;
164
+ borderRadius?: number;
165
+ }> = ({ size, thickness = 4, speed = 0.5, borderRadius = 9999 }) => {
166
+ const frame = useCurrentFrame();
167
+ const angle = (frame * speed) % 360;
168
+ return (
169
+ <div
170
+ style={{
171
+ position: "absolute",
172
+ inset: -thickness,
173
+ width: size + thickness * 2,
174
+ height: size + thickness * 2,
175
+ borderRadius: borderRadius + thickness,
176
+ background: `conic-gradient(from ${angle}deg, ${C.iriCyan}, ${C.iriViolet}, ${C.iriRose}, ${C.iriGold}, ${C.iriCyan})`,
177
+ padding: thickness,
178
+ pointerEvents: "none",
179
+ opacity: 0.85,
180
+ }}
181
+ >
182
+ <div style={{ width: "100%", height: "100%", background: C.bg, borderRadius }} />
183
+ </div>
184
+ );
185
+ };
186
+
187
+ export const GlassCard: React.FC<{
188
+ style?: React.CSSProperties;
189
+ children?: React.ReactNode;
190
+ radius?: number;
191
+ }> = ({ style, children, radius = 32 }) => (
192
+ <div style={{ ...glassBase, borderRadius: radius, ...style }}>{children}</div>
193
+ );
194
+
195
+ export const EyebrowPill: React.FC<{ children: React.ReactNode; dotColor?: string }> = ({
196
+ children,
197
+ dotColor = C.iriViolet,
198
+ }) => (
199
+ <div
200
+ style={{
201
+ ...glassBase,
202
+ borderRadius: 9999,
203
+ padding: "10px 22px",
204
+ display: "inline-flex",
205
+ alignItems: "center",
206
+ gap: 10,
207
+ fontFamily: MONO,
208
+ fontSize: 18,
209
+ fontWeight: 500,
210
+ color: C.inkSoft,
211
+ letterSpacing: "0.18em",
212
+ textTransform: "uppercase",
213
+ }}
214
+ >
215
+ <div
216
+ style={{
217
+ width: 7,
218
+ height: 7,
219
+ borderRadius: "50%",
220
+ background: dotColor,
221
+ boxShadow: `0 0 14px ${dotColor}`,
222
+ }}
223
+ />
224
+ {children}
225
+ </div>
226
+ );
227
+
228
+ // ═══════════════════════════════════════════════════════════════
229
+ // STAGGERED WORDS — fade-up with blur-clear (GSAP-style stagger)
230
+ // ═══════════════════════════════════════════════════════════════
231
+ export const StaggeredWords: React.FC<{
232
+ text: string;
233
+ startFrame: number;
234
+ perWordDelay?: number;
235
+ duration?: number;
236
+ fontSize: number;
237
+ fontWeight?: number;
238
+ color?: string;
239
+ letterSpacing?: string;
240
+ lineHeight?: number;
241
+ align?: "left" | "center" | "right";
242
+ fontFamily?: string;
243
+ highlight?: string;
244
+ highlightColor?: string;
245
+ }> = ({
246
+ text,
247
+ startFrame,
248
+ perWordDelay = 4,
249
+ duration = 24,
250
+ fontSize,
251
+ fontWeight = 700,
252
+ color = C.ink,
253
+ letterSpacing = "-0.035em",
254
+ lineHeight = 1.02,
255
+ align = "left",
256
+ fontFamily = FONT,
257
+ highlight,
258
+ highlightColor = C.iriViolet,
259
+ }) => {
260
+ const frame = useCurrentFrame();
261
+ const words = text.split(" ");
262
+ return (
263
+ <div
264
+ style={{
265
+ display: "flex",
266
+ flexWrap: "wrap",
267
+ gap: "0.25em",
268
+ justifyContent:
269
+ align === "center" ? "center" : align === "right" ? "flex-end" : "flex-start",
270
+ textAlign: align,
271
+ fontFamily,
272
+ fontSize,
273
+ fontWeight,
274
+ color,
275
+ letterSpacing,
276
+ lineHeight,
277
+ }}
278
+ >
279
+ {words.map((w, i) => {
280
+ const wordStart = startFrame + i * perWordDelay;
281
+ const local = frame - wordStart;
282
+ const opacity = interpolate(local, [0, duration], [0, 1], {
283
+ extrapolateLeft: "clamp",
284
+ extrapolateRight: "clamp",
285
+ easing: ease.expoOut,
286
+ });
287
+ const y = interpolate(local, [0, duration], [40, 0], {
288
+ extrapolateLeft: "clamp",
289
+ extrapolateRight: "clamp",
290
+ easing: ease.power3Out,
291
+ });
292
+ const blur = interpolate(local, [0, duration], [12, 0], {
293
+ extrapolateLeft: "clamp",
294
+ extrapolateRight: "clamp",
295
+ });
296
+ const isHighlight =
297
+ highlight && w.toLowerCase().replace(/[.,]/g, "").includes(highlight.toLowerCase());
298
+ return (
299
+ <span
300
+ key={i}
301
+ style={{
302
+ display: "inline-block",
303
+ opacity,
304
+ transform: `translateY(${y}px)`,
305
+ filter: `blur(${blur}px)`,
306
+ color: isHighlight ? highlightColor : color,
307
+ willChange: "transform, opacity, filter",
308
+ }}
309
+ >
310
+ {w}
311
+ </span>
312
+ );
313
+ })}
314
+ </div>
315
+ );
316
+ };
317
+
318
+ // ═══════════════════════════════════════════════════════════════
319
+ // COUNTER — easing number ticker, tabular nums
320
+ // ═══════════════════════════════════════════════════════════════
321
+ export const Counter: React.FC<{
322
+ from: number;
323
+ to: number;
324
+ startFrame: number;
325
+ duration: number;
326
+ format?: (n: number) => string;
327
+ style?: React.CSSProperties;
328
+ easing?: (n: number) => number;
329
+ }> = ({ from, to, startFrame, duration, format, style, easing = ease.expoOut }) => {
330
+ const frame = useCurrentFrame();
331
+ const v = interpolate(frame, [startFrame, startFrame + duration], [from, to], {
332
+ extrapolateLeft: "clamp",
333
+ extrapolateRight: "clamp",
334
+ easing,
335
+ });
336
+ return <span style={style}>{format ? format(v) : Math.round(v).toString()}</span>;
337
+ };
338
+
339
+ // ═══════════════════════════════════════════════════════════════
340
+ // MOTION-ART OPENERS — sonar, particles, beams, glyphs
341
+ // ═══════════════════════════════════════════════════════════════
342
+ export const SonarRings: React.FC<{ centerY?: number }> = ({ centerY }) => {
343
+ const frame = useCurrentFrame();
344
+ const rings = [0, 18, 36, 54, 72];
345
+ return (
346
+ <AbsoluteFill style={{ alignItems: "center", justifyContent: centerY ? "flex-start" : "center" }}>
347
+ {rings.map((birth, i) => {
348
+ const local = frame - birth;
349
+ if (local < 0) return null;
350
+ const cycle = local % 90;
351
+ const scale = interpolate(cycle, [0, 90], [0.05, 1.4], {
352
+ extrapolateLeft: "clamp",
353
+ extrapolateRight: "clamp",
354
+ easing: ease.expoOut,
355
+ });
356
+ const op = interpolate(cycle, [0, 30, 90], [0, 0.55, 0], {
357
+ extrapolateLeft: "clamp",
358
+ extrapolateRight: "clamp",
359
+ });
360
+ return (
361
+ <div
362
+ key={i}
363
+ style={{
364
+ position: "absolute",
365
+ top: centerY ?? "50%",
366
+ width: 1400,
367
+ height: 1400,
368
+ borderRadius: "50%",
369
+ border: `2px solid ${i % 2 === 0 ? C.iriViolet : C.iriCyan}`,
370
+ transform: `scale(${scale})`,
371
+ opacity: op,
372
+ willChange: "transform, opacity",
373
+ }}
374
+ />
375
+ );
376
+ })}
377
+ </AbsoluteFill>
378
+ );
379
+ };
380
+
381
+ export const ParticleBurst: React.FC<{ count?: number }> = ({ count = 48 }) => {
382
+ const frame = useCurrentFrame();
383
+ const particles = Array.from({ length: count }, (_, i) => {
384
+ const angle = (i / count) * Math.PI * 2 + i * 0.31;
385
+ const distance = 200 + (i % 7) * 80;
386
+ const size = 6 + (i % 4) * 4;
387
+ const colorIdx = i % 4;
388
+ const color = [C.iriCyan, C.iriViolet, C.iriRose, C.iriGold][colorIdx];
389
+ const delay = (i * 0.7) % 24;
390
+ return { angle, distance, size, color, delay };
391
+ });
392
+
393
+ return (
394
+ <AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
395
+ {particles.map((p, i) => {
396
+ const local = Math.max(0, frame - p.delay);
397
+ const burst = interpolate(local, [0, 50], [0, 1], {
398
+ extrapolateLeft: "clamp",
399
+ extrapolateRight: "clamp",
400
+ easing: ease.expoOut,
401
+ });
402
+ const drift = Math.sin((frame + i * 12) * 0.04) * 12;
403
+ const x = Math.cos(p.angle) * p.distance * burst + drift;
404
+ const y = Math.sin(p.angle) * p.distance * burst * 1.4 + drift * 0.6;
405
+ const op = interpolate(local, [0, 20, 100, 180], [0, 0.85, 0.5, 0.2], {
406
+ extrapolateLeft: "clamp",
407
+ extrapolateRight: "clamp",
408
+ });
409
+ return (
410
+ <div
411
+ key={i}
412
+ style={{
413
+ position: "absolute",
414
+ width: p.size,
415
+ height: p.size,
416
+ borderRadius: "50%",
417
+ background: p.color,
418
+ boxShadow: `0 0 ${p.size * 2}px ${p.color}`,
419
+ transform: `translate(${x}px, ${y}px)`,
420
+ opacity: op,
421
+ willChange: "transform, opacity",
422
+ }}
423
+ />
424
+ );
425
+ })}
426
+ </AbsoluteFill>
427
+ );
428
+ };
429
+
430
+ export const LightBeam: React.FC<{ delay: number; angle: number }> = ({ delay, angle }) => {
431
+ const frame = useCurrentFrame();
432
+ const local = frame - delay;
433
+ const progress = interpolate(local, [0, 60], [-0.3, 1.3], {
434
+ extrapolateLeft: "clamp",
435
+ extrapolateRight: "clamp",
436
+ easing: ease.expoOut,
437
+ });
438
+ const op = interpolate(local, [0, 12, 50, 60], [0, 0.7, 0.7, 0], {
439
+ extrapolateLeft: "clamp",
440
+ extrapolateRight: "clamp",
441
+ });
442
+ return (
443
+ <div
444
+ style={{
445
+ position: "absolute",
446
+ top: "50%",
447
+ left: "50%",
448
+ width: 2200,
449
+ height: 100,
450
+ background: `linear-gradient(90deg, transparent 0%, ${C.iriCyan}88 30%, ${C.iriViolet}cc 50%, ${C.iriRose}88 70%, transparent 100%)`,
451
+ filter: "blur(20px)",
452
+ transform: `translate(-50%, -50%) rotate(${angle}deg) translateX(${(progress - 0.5) * 1500}px)`,
453
+ opacity: op,
454
+ willChange: "transform, opacity",
455
+ }}
456
+ />
457
+ );
458
+ };
459
+
460
+ export const FloatingGlyphs: React.FC = () => {
461
+ const frame = useCurrentFrame();
462
+ const glyphs = [
463
+ { x: 120, y: 380, size: 80, delay: 4, rot: -8 },
464
+ { x: 880, y: 340, size: 60, delay: 12, rot: 12 },
465
+ { x: 80, y: 1240, size: 70, delay: 22, rot: 6 },
466
+ { x: 920, y: 1280, size: 90, delay: 32, rot: -10 },
467
+ { x: 200, y: 800, size: 50, delay: 42, rot: 14 },
468
+ { x: 820, y: 760, size: 55, delay: 52, rot: -4 },
469
+ ];
470
+ return (
471
+ <>
472
+ {glyphs.map((g, i) => {
473
+ const local = frame - g.delay;
474
+ const op = interpolate(local, [0, 30], [0, 1], {
475
+ extrapolateLeft: "clamp",
476
+ extrapolateRight: "clamp",
477
+ easing: ease.expoOut,
478
+ });
479
+ const float = Math.sin((frame + i * 22) * 0.05) * 8;
480
+ const scale = spring({
481
+ frame: local,
482
+ fps: 30,
483
+ config: { damping: 12, stiffness: 110 },
484
+ from: 0.6,
485
+ to: 1,
486
+ });
487
+ return (
488
+ <div
489
+ key={i}
490
+ style={{
491
+ position: "absolute",
492
+ left: g.x,
493
+ top: g.y + float,
494
+ width: g.size,
495
+ height: g.size,
496
+ borderRadius: g.size * 0.28,
497
+ background: C.glassFill,
498
+ backdropFilter: "blur(20px) saturate(160%)",
499
+ WebkitBackdropFilter: "blur(20px) saturate(160%)",
500
+ border: `1px solid ${C.glassBorder}`,
501
+ boxShadow:
502
+ "inset 0 1px 0 rgba(255,255,255,0.85), 0 8px 16px -4px rgba(120,100,180,0.18)",
503
+ transform: `rotate(${g.rot}deg) scale(${scale})`,
504
+ opacity: op * 0.85,
505
+ willChange: "transform, opacity",
506
+ }}
507
+ />
508
+ );
509
+ })}
510
+ </>
511
+ );
512
+ };
513
+
514
+ // ═══════════════════════════════════════════════════════════════
515
+ // YC SIGIL — orange Y mark on glass (motion-art accent)
516
+ // ═══════════════════════════════════════════════════════════════
517
+ export const YCSigil: React.FC<{ size?: number }> = ({ size = 96 }) => (
518
+ <div
519
+ style={{
520
+ width: size,
521
+ height: size,
522
+ borderRadius: size * 0.18,
523
+ background: C.ycOrange,
524
+ display: "flex",
525
+ alignItems: "center",
526
+ justifyContent: "center",
527
+ color: "#FFF",
528
+ fontFamily: FONT,
529
+ fontWeight: 700,
530
+ fontSize: size * 0.5,
531
+ letterSpacing: "-0.04em",
532
+ boxShadow: `0 12px 28px -8px ${C.ycOrange}99, inset 0 1px 0 rgba(255,255,255,0.4)`,
533
+ }}
534
+ >
535
+ Y
536
+ </div>
537
+ );
538
+
539
+ // ═══════════════════════════════════════════════════════════════
540
+ // SCENE 1 — HOOK (0-180, 6s)
541
+ // "The president of Y Combinator just open sourced his entire Claude Code setup."
542
+ // ═══════════════════════════════════════════════════════════════
543
+ const HookScene: React.FC = () => {
544
+ const frame = useCurrentFrame();
545
+
546
+ const heroScale = spring({
547
+ frame: frame - 6,
548
+ fps: 30,
549
+ config: { damping: 14, stiffness: 130 },
550
+ from: 0.7,
551
+ to: 1,
552
+ });
553
+ const heroBlur = interpolate(frame, [6, 32], [16, 0], {
554
+ extrapolateLeft: "clamp",
555
+ extrapolateRight: "clamp",
556
+ easing: ease.expoOut,
557
+ });
558
+
559
+ // YC sigil floats top-right, scale-pops at 18f
560
+ const ycScale = spring({
561
+ frame: frame - 18,
562
+ fps: 30,
563
+ config: { damping: 11, stiffness: 130 },
564
+ from: 0,
565
+ to: 1,
566
+ });
567
+ const ycFloat = Math.sin(frame * 0.05) * 10;
568
+
569
+ return (
570
+ <AbsoluteFill>
571
+ <SonarRings />
572
+ <LightBeam delay={0} angle={-18} />
573
+ <LightBeam delay={26} angle={22} />
574
+ <LightBeam delay={60} angle={-12} />
575
+ <FloatingGlyphs />
576
+ <ParticleBurst />
577
+
578
+ {/* YC sigil — top-right floating accent */}
579
+ <div
580
+ style={{
581
+ position: "absolute",
582
+ top: SAFE_TOP - 60,
583
+ right: 80,
584
+ transform: `scale(${ycScale}) translateY(${ycFloat}px) rotate(-6deg)`,
585
+ willChange: "transform",
586
+ }}
587
+ >
588
+ <YCSigil size={120} />
589
+ </div>
590
+
591
+ {/* Eyebrow */}
592
+ <div
593
+ style={{
594
+ position: "absolute",
595
+ top: SAFE_TOP + 50,
596
+ left: 0,
597
+ right: 0,
598
+ display: "flex",
599
+ justifyContent: "center",
600
+ opacity: interpolate(frame, [4, 24], [0, 1], { extrapolateRight: "clamp" }),
601
+ transform: `translateY(${interpolate(frame, [4, 24], [16, 0], { extrapolateRight: "clamp", easing: ease.power3Out })}px)`,
602
+ }}
603
+ >
604
+ <EyebrowPill dotColor={C.ycOrange}>01 — heads up</EyebrowPill>
605
+ </div>
606
+
607
+ {/* Hero copy — center, scale-pop + blur clear */}
608
+ <AbsoluteFill
609
+ style={{
610
+ alignItems: "center",
611
+ justifyContent: "center",
612
+ padding: "0 64px",
613
+ transform: `scale(${heroScale})`,
614
+ filter: `blur(${heroBlur}px)`,
615
+ willChange: "transform, filter",
616
+ }}
617
+ >
618
+ <div style={{ width: "100%" }}>
619
+ <StaggeredWords
620
+ text="The president of"
621
+ startFrame={6}
622
+ perWordDelay={3}
623
+ fontSize={84}
624
+ fontWeight={500}
625
+ color={C.inkMuted}
626
+ align="center"
627
+ letterSpacing="-0.025em"
628
+ />
629
+ <div style={{ height: 4 }} />
630
+ <StaggeredWords
631
+ text="Y Combinator"
632
+ startFrame={20}
633
+ perWordDelay={3}
634
+ fontSize={148}
635
+ fontWeight={800}
636
+ color={C.ink}
637
+ align="center"
638
+ letterSpacing="-0.05em"
639
+ lineHeight={1}
640
+ highlight="combinator"
641
+ highlightColor={C.ycOrange}
642
+ />
643
+ <div style={{ height: 14 }} />
644
+ <StaggeredWords
645
+ text="just open sourced"
646
+ startFrame={42}
647
+ perWordDelay={3}
648
+ fontSize={84}
649
+ fontWeight={500}
650
+ color={C.inkMuted}
651
+ align="center"
652
+ letterSpacing="-0.025em"
653
+ />
654
+ <div style={{ height: 4 }} />
655
+ <StaggeredWords
656
+ text="his Claude Code setup."
657
+ startFrame={62}
658
+ perWordDelay={3}
659
+ fontSize={84}
660
+ fontWeight={700}
661
+ color={C.ink}
662
+ align="center"
663
+ letterSpacing="-0.03em"
664
+ />
665
+ </div>
666
+
667
+ {/* Iridescent divider accent */}
668
+ <div
669
+ style={{
670
+ width: 280,
671
+ height: 5,
672
+ borderRadius: 999,
673
+ margin: "48px 0",
674
+ background: `linear-gradient(90deg, ${C.iriCyan}, ${C.iriViolet}, ${C.iriRose})`,
675
+ opacity: interpolate(frame, [80, 110], [0, 1], { extrapolateRight: "clamp" }),
676
+ transform: `scaleX(${interpolate(frame, [80, 120], [0, 1], { extrapolateRight: "clamp", easing: ease.expoOut })})`,
677
+ transformOrigin: "center",
678
+ boxShadow: `0 0 24px ${C.iriViolet}66`,
679
+ }}
680
+ />
681
+ </AbsoluteFill>
682
+ </AbsoluteFill>
683
+ );
684
+ };
685
+
686
+ // ═══════════════════════════════════════════════════════════════
687
+ // SCENE 2 — REVEAL (180-390, 7s) — "The repo is called gstack."
688
+ // GitHub repo card materializes
689
+ // ═══════════════════════════════════════════════════════════════
690
+ export const RepoCard: React.FC<{ startFrame: number }> = ({ startFrame }) => {
691
+ const frame = useCurrentFrame();
692
+ const local = frame - startFrame;
693
+ const op = interpolate(local, [0, 14], [0, 1], { extrapolateRight: "clamp" });
694
+ const y = interpolate(local, [0, 20], [60, 0], {
695
+ extrapolateRight: "clamp",
696
+ easing: ease.expoOut,
697
+ });
698
+ const tilt = interpolate(local, [0, 30], [-3, 0], {
699
+ extrapolateRight: "clamp",
700
+ easing: ease.power3Out,
701
+ });
702
+
703
+ // Star counter
704
+ return (
705
+ <div
706
+ style={{
707
+ ...glassBase,
708
+ borderRadius: 32,
709
+ width: 880,
710
+ padding: "40px 44px",
711
+ opacity: op,
712
+ transform: `translateY(${y}px) rotate(${tilt}deg) perspective(1400px) rotateX(2deg)`,
713
+ willChange: "transform",
714
+ }}
715
+ >
716
+ {/* Top row — github octicon + path */}
717
+ <div style={{ display: "flex", alignItems: "center", gap: 16, marginBottom: 24 }}>
718
+ <svg width="34" height="34" viewBox="0 0 24 24" fill={C.ink}>
719
+ <path d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.11.79-.25.79-.56v-2.01c-3.2.7-3.87-1.54-3.87-1.54-.52-1.32-1.27-1.67-1.27-1.67-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.02 1.75 2.68 1.24 3.34.95.1-.74.4-1.24.72-1.53-2.55-.29-5.24-1.27-5.24-5.65 0-1.25.45-2.27 1.18-3.07-.12-.29-.51-1.46.11-3.05 0 0 .96-.31 3.15 1.17.91-.25 1.89-.38 2.86-.38.97 0 1.95.13 2.86.38 2.18-1.48 3.14-1.17 3.14-1.17.62 1.59.23 2.76.11 3.05.74.8 1.18 1.83 1.18 3.07 0 4.39-2.7 5.36-5.27 5.64.41.36.78 1.06.78 2.13v3.16c0 .31.21.67.79.56 4.57-1.52 7.85-5.83 7.85-10.91C23.5 5.65 18.35.5 12 .5z" />
720
+ </svg>
721
+ <div
722
+ style={{
723
+ fontFamily: MONO,
724
+ fontSize: 30,
725
+ fontWeight: 500,
726
+ color: C.inkSoft,
727
+ letterSpacing: "-0.01em",
728
+ }}
729
+ >
730
+ garrytan
731
+ <span style={{ color: C.inkDim }}>/</span>
732
+ <span style={{ fontWeight: 700, color: C.ink }}>gstack</span>
733
+ </div>
734
+ <div
735
+ style={{
736
+ marginLeft: "auto",
737
+ ...glassBase,
738
+ borderRadius: 9999,
739
+ padding: "6px 14px",
740
+ fontFamily: MONO,
741
+ fontSize: 14,
742
+ color: C.iriViolet,
743
+ letterSpacing: "0.18em",
744
+ textTransform: "uppercase",
745
+ fontWeight: 600,
746
+ }}
747
+ >
748
+ public · MIT
749
+ </div>
750
+ </div>
751
+
752
+ {/* gstack big wordmark */}
753
+ <div
754
+ style={{
755
+ fontFamily: FONT,
756
+ fontSize: 200,
757
+ fontWeight: 800,
758
+ color: C.ink,
759
+ letterSpacing: "-0.07em",
760
+ lineHeight: 0.92,
761
+ background: `linear-gradient(135deg, ${C.ink} 0%, ${C.inkSoft} 45%, ${C.iriViolet} 100%)`,
762
+ WebkitBackgroundClip: "text",
763
+ WebkitTextFillColor: "transparent",
764
+ backgroundClip: "text",
765
+ }}
766
+ >
767
+ gstack
768
+ </div>
769
+
770
+ {/* Description */}
771
+ <div
772
+ style={{
773
+ fontFamily: FONT,
774
+ fontSize: 26,
775
+ fontWeight: 500,
776
+ color: C.inkMuted,
777
+ marginTop: 6,
778
+ lineHeight: 1.3,
779
+ letterSpacing: "-0.01em",
780
+ }}
781
+ >
782
+ Turn Claude Code into your engineering team.
783
+ </div>
784
+
785
+ {/* Stats row — stars, forks, language */}
786
+ <div
787
+ style={{
788
+ marginTop: 28,
789
+ display: "flex",
790
+ gap: 28,
791
+ alignItems: "center",
792
+ fontFamily: MONO,
793
+ fontSize: 20,
794
+ color: C.inkSoft,
795
+ }}
796
+ >
797
+ <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
798
+ <svg width="20" height="20" viewBox="0 0 24 24" fill={C.iriGold}>
799
+ <polygon points="12 2 15 8.5 22 9.3 17 14 18.5 21 12 17.5 5.5 21 7 14 2 9.3 9 8.5" />
800
+ </svg>
801
+ <Counter
802
+ from={0}
803
+ to={85000}
804
+ startFrame={startFrame + 14}
805
+ duration={28}
806
+ format={(n) => Math.round(n).toLocaleString()}
807
+ style={{ fontVariantNumeric: "tabular-nums", fontWeight: 600 }}
808
+ />
809
+ </div>
810
+ <div style={{ width: 1, height: 22, background: C.hairline }} />
811
+ <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
812
+ <div style={{ width: 14, height: 14, borderRadius: 7, background: C.iriCyan }} />
813
+ Markdown
814
+ </div>
815
+ <div style={{ width: 1, height: 22, background: C.hairline }} />
816
+ <div style={{ color: C.iriViolet, fontWeight: 600 }}>open source</div>
817
+ </div>
818
+ </div>
819
+ );
820
+ };
821
+
822
+ const RevealScene: React.FC = () => {
823
+ const frame = useCurrentFrame();
824
+ const local = frame - BEAT.reveal;
825
+
826
+ return (
827
+ <AbsoluteFill>
828
+ {/* Eyebrow */}
829
+ <div
830
+ style={{
831
+ position: "absolute",
832
+ top: SAFE_TOP + 24,
833
+ left: 0,
834
+ right: 0,
835
+ display: "flex",
836
+ justifyContent: "center",
837
+ opacity: interpolate(local, [0, 12], [0, 1], { extrapolateRight: "clamp" }),
838
+ }}
839
+ >
840
+ <EyebrowPill>02 — the repo</EyebrowPill>
841
+ </div>
842
+
843
+ {/* Repo card — fast materialize so the 1.9s window holds the brand reveal */}
844
+ <AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
845
+ <RepoCard startFrame={BEAT.reveal + 4} />
846
+ </AbsoluteFill>
847
+ </AbsoluteFill>
848
+ );
849
+ };
850
+
851
+ // ═══════════════════════════════════════════════════════════════
852
+ // SCENE 3 — PITCH (390-720, 11s) — "23 specialists working for you"
853
+ // gstack.mov clip in tilted laptop frame + specialist counter
854
+ // ═══════════════════════════════════════════════════════════════
855
+ const PitchScene: React.FC = () => {
856
+ const frame = useCurrentFrame();
857
+ const local = frame - BEAT.pitch;
858
+
859
+ const clipDuration = BEAT.officeHours - BEAT.pitch;
860
+
861
+ // Clip frame
862
+ const clipOp = interpolate(local, [0, 26], [0, 1], { extrapolateRight: "clamp" });
863
+ const clipY = interpolate(local, [0, 32], [60, 0], {
864
+ extrapolateRight: "clamp",
865
+ easing: ease.expoOut,
866
+ });
867
+ const clipTilt = interpolate(local, [0, 50], [-2, 0], {
868
+ extrapolateRight: "clamp",
869
+ easing: ease.power3Out,
870
+ });
871
+
872
+ // Big "23" counter pops in late
873
+ const numScale = spring({
874
+ frame: local - 110,
875
+ fps: 30,
876
+ config: { damping: 12, stiffness: 110 },
877
+ from: 0.5,
878
+ to: 1,
879
+ });
880
+ const numOp = interpolate(local, [110, 130], [0, 1], { extrapolateRight: "clamp" });
881
+
882
+ return (
883
+ <AbsoluteFill>
884
+ {/* Eyebrow */}
885
+ <div
886
+ style={{
887
+ position: "absolute",
888
+ top: SAFE_TOP + 24,
889
+ left: 0,
890
+ right: 0,
891
+ display: "flex",
892
+ justifyContent: "center",
893
+ opacity: interpolate(local, [0, 20], [0, 1], { extrapolateRight: "clamp" }),
894
+ }}
895
+ >
896
+ <EyebrowPill>03 — install once</EyebrowPill>
897
+ </div>
898
+
899
+ {/* Headline */}
900
+ <div style={{ position: "absolute", left: 80, right: 80, top: SAFE_TOP + 110 }}>
901
+ <StaggeredWords
902
+ text="Install once."
903
+ startFrame={BEAT.pitch + 6}
904
+ fontSize={84}
905
+ fontWeight={500}
906
+ color={C.inkMuted}
907
+ align="left"
908
+ />
909
+ <div style={{ height: 4 }} />
910
+ <StaggeredWords
911
+ text="Get a team of"
912
+ startFrame={BEAT.pitch + 24}
913
+ fontSize={102}
914
+ fontWeight={800}
915
+ color={C.ink}
916
+ align="left"
917
+ letterSpacing="-0.045em"
918
+ />
919
+ </div>
920
+
921
+ {/* Tilted glass laptop frame around clip */}
922
+ <div
923
+ style={{
924
+ position: "absolute",
925
+ left: 60,
926
+ right: 60,
927
+ top: 800,
928
+ height: 480,
929
+ opacity: clipOp,
930
+ transform: `translateY(${clipY}px) rotate(${clipTilt}deg) perspective(1400px) rotateX(4deg)`,
931
+ willChange: "transform",
932
+ }}
933
+ >
934
+ <div
935
+ style={{
936
+ ...glassBase,
937
+ borderRadius: 28,
938
+ padding: 14,
939
+ width: "100%",
940
+ height: "100%",
941
+ position: "relative",
942
+ }}
943
+ >
944
+ {/* Window controls */}
945
+ <div style={{ display: "flex", gap: 8, padding: "4px 6px 12px", alignItems: "center" }}>
946
+ <div style={{ width: 12, height: 12, borderRadius: 6, background: "#FF5F57" }} />
947
+ <div style={{ width: 12, height: 12, borderRadius: 6, background: "#FEBC2E" }} />
948
+ <div style={{ width: 12, height: 12, borderRadius: 6, background: "#28C840" }} />
949
+ <div
950
+ style={{
951
+ marginLeft: "auto",
952
+ fontFamily: MONO,
953
+ fontSize: 13,
954
+ color: C.inkDim,
955
+ letterSpacing: "0.05em",
956
+ }}
957
+ >
958
+ ~/.claude/skills/gstack
959
+ </div>
960
+ </div>
961
+
962
+ {/* Video — wrap in inner Sequence so first frame isn't frozen */}
963
+ <Sequence from={BEAT.pitch} durationInFrames={clipDuration}>
964
+ <div
965
+ style={{
966
+ width: "100%",
967
+ height: "calc(100% - 28px)",
968
+ borderRadius: 18,
969
+ overflow: "hidden",
970
+ background: "#0E0E12",
971
+ }}
972
+ >
973
+ {/* REFERENCE-STRIP: <OffthreadVideo> removed — bring your own clip */}
974
+ </div>
975
+ </Sequence>
976
+ </div>
977
+ </div>
978
+
979
+ {/* Big 23 counter + specialists label */}
980
+ <div
981
+ style={{
982
+ position: "absolute",
983
+ left: 0,
984
+ right: 0,
985
+ top: 1310,
986
+ display: "flex",
987
+ alignItems: "baseline",
988
+ justifyContent: "center",
989
+ gap: 22,
990
+ opacity: numOp,
991
+ transform: `scale(${numScale})`,
992
+ willChange: "transform",
993
+ }}
994
+ >
995
+ <div
996
+ style={{
997
+ fontFamily: MONO,
998
+ fontSize: 200,
999
+ fontWeight: 700,
1000
+ color: C.ink,
1001
+ letterSpacing: "-0.07em",
1002
+ lineHeight: 0.9,
1003
+ fontVariantNumeric: "tabular-nums",
1004
+ background: `linear-gradient(135deg, ${C.iriCyan}, ${C.iriViolet}, ${C.iriRose})`,
1005
+ WebkitBackgroundClip: "text",
1006
+ WebkitTextFillColor: "transparent",
1007
+ backgroundClip: "text",
1008
+ }}
1009
+ >
1010
+ <Counter
1011
+ from={1}
1012
+ to={23}
1013
+ startFrame={BEAT.pitch + 110}
1014
+ duration={70}
1015
+ format={(n) => Math.round(n).toString()}
1016
+ />
1017
+ </div>
1018
+ <div
1019
+ style={{
1020
+ fontFamily: FONT,
1021
+ fontSize: 56,
1022
+ fontWeight: 700,
1023
+ color: C.ink,
1024
+ letterSpacing: "-0.025em",
1025
+ }}
1026
+ >
1027
+ specialists.
1028
+ </div>
1029
+ </div>
1030
+
1031
+ {/* Sub caption — sits comfortably below the counter */}
1032
+ <div
1033
+ style={{
1034
+ position: "absolute",
1035
+ left: 80,
1036
+ right: 80,
1037
+ top: 1500,
1038
+ textAlign: "center",
1039
+ opacity: interpolate(local, [180, 220], [0, 1], { extrapolateRight: "clamp" }),
1040
+ transform: `translateY(${interpolate(local, [180, 220], [16, 0], { extrapolateRight: "clamp", easing: ease.power3Out })}px)`,
1041
+ }}
1042
+ >
1043
+ <div
1044
+ style={{
1045
+ fontFamily: FONT,
1046
+ fontSize: 30,
1047
+ fontWeight: 500,
1048
+ color: C.inkMuted,
1049
+ letterSpacing: "-0.01em",
1050
+ }}
1051
+ >
1052
+ Each one with their own job.
1053
+ </div>
1054
+ </div>
1055
+ </AbsoluteFill>
1056
+ );
1057
+ };
1058
+
1059
+ // ═══════════════════════════════════════════════════════════════
1060
+ // SCENE 4 — OFFICEHOURS (720-960, 8s)
1061
+ // "Type /office-hours, describe your idea, AI pushes back."
1062
+ // ═══════════════════════════════════════════════════════════════
1063
+ export const Terminal: React.FC<{
1064
+ startFrame: number;
1065
+ command: string;
1066
+ responseLines: { text: string; color?: string; delay: number; mono?: boolean }[];
1067
+ }> = ({ startFrame, command, responseLines }) => {
1068
+ const frame = useCurrentFrame();
1069
+ const local = frame - startFrame;
1070
+
1071
+ // Type out command
1072
+ const typeChars = Math.max(0, Math.floor(interpolate(local, [10, 50], [0, command.length], {
1073
+ extrapolateLeft: "clamp",
1074
+ extrapolateRight: "clamp",
1075
+ })));
1076
+ const typedCommand = command.slice(0, typeChars);
1077
+
1078
+ // Cursor blink
1079
+ const cursorOn = Math.floor(frame / 10) % 2 === 0;
1080
+
1081
+ return (
1082
+ <div
1083
+ style={{
1084
+ ...glassBase,
1085
+ borderRadius: 28,
1086
+ padding: "20px 28px 28px",
1087
+ width: "100%",
1088
+ position: "relative",
1089
+ }}
1090
+ >
1091
+ {/* Window controls */}
1092
+ <div
1093
+ style={{
1094
+ display: "flex",
1095
+ alignItems: "center",
1096
+ gap: 8,
1097
+ marginBottom: 22,
1098
+ }}
1099
+ >
1100
+ <div style={{ width: 12, height: 12, borderRadius: 6, background: "#FF5F57" }} />
1101
+ <div style={{ width: 12, height: 12, borderRadius: 6, background: "#FEBC2E" }} />
1102
+ <div style={{ width: 12, height: 12, borderRadius: 6, background: "#28C840" }} />
1103
+ <div
1104
+ style={{
1105
+ marginLeft: "auto",
1106
+ fontFamily: MONO,
1107
+ fontSize: 13,
1108
+ color: C.inkDim,
1109
+ letterSpacing: "0.05em",
1110
+ }}
1111
+ >
1112
+ claude code · gstack
1113
+ </div>
1114
+ </div>
1115
+
1116
+ {/* Prompt + command */}
1117
+ <div
1118
+ style={{
1119
+ fontFamily: MONO,
1120
+ fontSize: 26,
1121
+ color: C.ink,
1122
+ letterSpacing: "-0.005em",
1123
+ lineHeight: 1.5,
1124
+ display: "flex",
1125
+ alignItems: "center",
1126
+ gap: 10,
1127
+ }}
1128
+ >
1129
+ <span style={{ color: C.iriViolet }}>›</span>
1130
+ <span style={{ color: C.iriCyan, fontWeight: 600 }}>{typedCommand}</span>
1131
+ {typeChars < command.length && cursorOn && (
1132
+ <span
1133
+ style={{
1134
+ display: "inline-block",
1135
+ width: 12,
1136
+ height: 22,
1137
+ background: C.ink,
1138
+ marginLeft: 2,
1139
+ }}
1140
+ />
1141
+ )}
1142
+ </div>
1143
+
1144
+ {/* Response lines */}
1145
+ <div style={{ marginTop: 18 }}>
1146
+ {responseLines.map((line, i) => {
1147
+ const lineLocal = local - line.delay;
1148
+ const op = interpolate(lineLocal, [0, 14], [0, 1], {
1149
+ extrapolateLeft: "clamp",
1150
+ extrapolateRight: "clamp",
1151
+ });
1152
+ const x = interpolate(lineLocal, [0, 18], [-12, 0], {
1153
+ extrapolateLeft: "clamp",
1154
+ extrapolateRight: "clamp",
1155
+ easing: ease.expoOut,
1156
+ });
1157
+ return (
1158
+ <div
1159
+ key={i}
1160
+ style={{
1161
+ fontFamily: line.mono === false ? FONT : MONO,
1162
+ fontSize: 22,
1163
+ color: line.color ?? C.inkSoft,
1164
+ letterSpacing: "-0.005em",
1165
+ lineHeight: 1.5,
1166
+ opacity: op,
1167
+ transform: `translateX(${x}px)`,
1168
+ marginTop: 8,
1169
+ }}
1170
+ >
1171
+ {line.text}
1172
+ </div>
1173
+ );
1174
+ })}
1175
+ </div>
1176
+ </div>
1177
+ );
1178
+ };
1179
+
1180
+ const OfficeHoursScene: React.FC = () => {
1181
+ const frame = useCurrentFrame();
1182
+ const local = frame - BEAT.officeHours;
1183
+
1184
+ return (
1185
+ <AbsoluteFill>
1186
+ <div
1187
+ style={{
1188
+ position: "absolute",
1189
+ top: SAFE_TOP + 24,
1190
+ left: 0,
1191
+ right: 0,
1192
+ display: "flex",
1193
+ justifyContent: "center",
1194
+ opacity: interpolate(local, [0, 20], [0, 1], { extrapolateRight: "clamp" }),
1195
+ }}
1196
+ >
1197
+ <EyebrowPill>04 — /office-hours</EyebrowPill>
1198
+ </div>
1199
+
1200
+ {/* Headline */}
1201
+ <div style={{ position: "absolute", left: 80, right: 80, top: SAFE_TOP + 110 }}>
1202
+ <StaggeredWords
1203
+ text="The AI"
1204
+ startFrame={BEAT.officeHours + 6}
1205
+ fontSize={88}
1206
+ fontWeight={500}
1207
+ color={C.inkMuted}
1208
+ align="left"
1209
+ />
1210
+ <div style={{ height: 4 }} />
1211
+ <StaggeredWords
1212
+ text="pushes back."
1213
+ startFrame={BEAT.officeHours + 18}
1214
+ fontSize={120}
1215
+ fontWeight={800}
1216
+ color={C.ink}
1217
+ align="left"
1218
+ letterSpacing="-0.05em"
1219
+ highlight="pushes"
1220
+ highlightColor={C.iriViolet}
1221
+ />
1222
+ </div>
1223
+
1224
+ {/* Terminal */}
1225
+ <div style={{ position: "absolute", left: 60, right: 60, top: 760 }}>
1226
+ <Terminal
1227
+ startFrame={BEAT.officeHours + 50}
1228
+ command="/office-hours"
1229
+ responseLines={[
1230
+ {
1231
+ text: " YC OFFICE HOURS — listening for your pitch",
1232
+ color: C.inkDim,
1233
+ delay: 60,
1234
+ },
1235
+ { text: "", delay: 70 },
1236
+ {
1237
+ text: " six forcing questions:",
1238
+ color: C.iriViolet,
1239
+ delay: 78,
1240
+ },
1241
+ { text: " ▸ what's the actual pain?", color: C.inkSoft, delay: 100 },
1242
+ { text: " ▸ who's already paying for this?", color: C.inkSoft, delay: 118 },
1243
+ { text: " ▸ what would 10× look like?", color: C.inkSoft, delay: 136 },
1244
+ { text: " ▸ where's the wedge?", color: C.inkSoft, delay: 154 },
1245
+ { text: " ▸ what could break this?", color: C.inkSoft, delay: 172 },
1246
+ { text: " ▸ what's the smallest thing to ship?", color: C.inkSoft, delay: 190 },
1247
+ ]}
1248
+ />
1249
+ </div>
1250
+ </AbsoluteFill>
1251
+ );
1252
+ };
1253
+
1254
+ // ═══════════════════════════════════════════════════════════════
1255
+ // SCENE 5 — REFRAME (960-1290, 11s)
1256
+ // "daily briefing app" → "personal chief of staff AI"
1257
+ // ═══════════════════════════════════════════════════════════════
1258
+ const ReframeScene: React.FC = () => {
1259
+ const frame = useCurrentFrame();
1260
+ const local = frame - BEAT.reframe;
1261
+
1262
+ // Card 1 (your idea) appears first
1263
+ const card1Op = interpolate(local, [40, 70], [0, 1], { extrapolateRight: "clamp" });
1264
+ const card1Scale = spring({
1265
+ frame: local - 40,
1266
+ fps: 30,
1267
+ config: { damping: 12, stiffness: 110 },
1268
+ from: 0.85,
1269
+ to: 1,
1270
+ });
1271
+ // Greys out at 130
1272
+ const card1Fade = interpolate(local, [150, 200], [1, 0.32], {
1273
+ extrapolateLeft: "clamp",
1274
+ extrapolateRight: "clamp",
1275
+ });
1276
+
1277
+ // Arrow morphs in at 140
1278
+ const arrowOp = interpolate(local, [140, 180], [0, 1], { extrapolateRight: "clamp" });
1279
+
1280
+ // Card 2 (reframe) appears at 170
1281
+ const card2Op = interpolate(local, [170, 210], [0, 1], { extrapolateRight: "clamp" });
1282
+ const card2Scale = spring({
1283
+ frame: local - 170,
1284
+ fps: 30,
1285
+ config: { damping: 11, stiffness: 130 },
1286
+ from: 0.7,
1287
+ to: 1,
1288
+ });
1289
+
1290
+ return (
1291
+ <AbsoluteFill>
1292
+ <div
1293
+ style={{
1294
+ position: "absolute",
1295
+ top: SAFE_TOP + 24,
1296
+ left: 0,
1297
+ right: 0,
1298
+ display: "flex",
1299
+ justifyContent: "center",
1300
+ opacity: interpolate(local, [0, 20], [0, 1], { extrapolateRight: "clamp" }),
1301
+ }}
1302
+ >
1303
+ <EyebrowPill>05 — reframe</EyebrowPill>
1304
+ </div>
1305
+
1306
+ {/* Headline */}
1307
+ <div style={{ position: "absolute", left: 80, right: 80, top: SAFE_TOP + 110 }}>
1308
+ <StaggeredWords
1309
+ text="It reframes"
1310
+ startFrame={BEAT.reframe + 6}
1311
+ fontSize={84}
1312
+ fontWeight={500}
1313
+ color={C.inkMuted}
1314
+ align="left"
1315
+ />
1316
+ <div style={{ height: 4 }} />
1317
+ <StaggeredWords
1318
+ text="the whole thing."
1319
+ startFrame={BEAT.reframe + 22}
1320
+ fontSize={104}
1321
+ fontWeight={800}
1322
+ color={C.ink}
1323
+ align="left"
1324
+ letterSpacing="-0.045em"
1325
+ highlight="whole"
1326
+ highlightColor={C.iriViolet}
1327
+ />
1328
+ </div>
1329
+
1330
+ {/* Card 1 — "daily briefing app" */}
1331
+ <div
1332
+ style={{
1333
+ position: "absolute",
1334
+ left: 80,
1335
+ right: 80,
1336
+ top: 740,
1337
+ opacity: card1Op * card1Fade,
1338
+ transform: `scale(${card1Scale})`,
1339
+ willChange: "transform",
1340
+ }}
1341
+ >
1342
+ <GlassCard radius={28} style={{ padding: "26px 32px" }}>
1343
+ <div
1344
+ style={{
1345
+ fontFamily: MONO,
1346
+ fontSize: 16,
1347
+ color: C.inkDim,
1348
+ letterSpacing: "0.18em",
1349
+ textTransform: "uppercase",
1350
+ fontWeight: 600,
1351
+ marginBottom: 12,
1352
+ }}
1353
+ >
1354
+ you said
1355
+ </div>
1356
+ <div
1357
+ style={{
1358
+ fontFamily: FONT,
1359
+ fontSize: 64,
1360
+ fontWeight: 700,
1361
+ color: C.inkSoft,
1362
+ letterSpacing: "-0.035em",
1363
+ lineHeight: 1.05,
1364
+ }}
1365
+ >
1366
+ "I want to build a
1367
+ <br />
1368
+ daily briefing app."
1369
+ </div>
1370
+ </GlassCard>
1371
+ </div>
1372
+
1373
+ {/* Morph arrow / connector */}
1374
+ <div
1375
+ style={{
1376
+ position: "absolute",
1377
+ left: 0,
1378
+ right: 0,
1379
+ top: 1060,
1380
+ display: "flex",
1381
+ justifyContent: "center",
1382
+ opacity: arrowOp,
1383
+ }}
1384
+ >
1385
+ <div
1386
+ style={{
1387
+ ...glassBase,
1388
+ borderRadius: 9999,
1389
+ padding: "16px 28px",
1390
+ display: "inline-flex",
1391
+ alignItems: "center",
1392
+ gap: 14,
1393
+ fontFamily: MONO,
1394
+ fontSize: 18,
1395
+ fontWeight: 600,
1396
+ color: C.iriViolet,
1397
+ letterSpacing: "0.18em",
1398
+ textTransform: "uppercase",
1399
+ }}
1400
+ >
1401
+ no — push back
1402
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke={C.iriViolet} strokeWidth="2.4">
1403
+ <line x1="3" y1="12" x2="21" y2="12" />
1404
+ <polyline points="13 5 21 12 13 19" />
1405
+ </svg>
1406
+ </div>
1407
+ </div>
1408
+
1409
+ {/* Card 2 — "personal chief of staff AI" */}
1410
+ <div
1411
+ style={{
1412
+ position: "absolute",
1413
+ left: 80,
1414
+ right: 80,
1415
+ top: 1170,
1416
+ opacity: card2Op,
1417
+ transform: `scale(${card2Scale})`,
1418
+ willChange: "transform",
1419
+ }}
1420
+ >
1421
+ <div
1422
+ style={{
1423
+ ...glassBase,
1424
+ borderRadius: 28,
1425
+ padding: "30px 34px",
1426
+ border: `2px solid ${C.iriViolet}`,
1427
+ boxShadow: `0 24px 48px -12px ${C.iriViolet}55, inset 0 1.5px 0 rgba(255,255,255,0.95)`,
1428
+ }}
1429
+ >
1430
+ <div
1431
+ style={{
1432
+ fontFamily: MONO,
1433
+ fontSize: 16,
1434
+ color: C.iriViolet,
1435
+ letterSpacing: "0.18em",
1436
+ textTransform: "uppercase",
1437
+ fontWeight: 600,
1438
+ marginBottom: 12,
1439
+ }}
1440
+ >
1441
+ you're actually building
1442
+ </div>
1443
+ <div
1444
+ style={{
1445
+ fontFamily: FONT,
1446
+ fontSize: 76,
1447
+ fontWeight: 800,
1448
+ color: C.ink,
1449
+ letterSpacing: "-0.045em",
1450
+ lineHeight: 1.02,
1451
+ }}
1452
+ >
1453
+ a personal chief
1454
+ <br />
1455
+ of staff AI.
1456
+ </div>
1457
+ </div>
1458
+ </div>
1459
+ </AbsoluteFill>
1460
+ );
1461
+ };
1462
+
1463
+ // ═══════════════════════════════════════════════════════════════
1464
+ // SCENE 6 — QA (1290-1620, 11s)
1465
+ // "/qa opens real Chrome, finds bugs, fixes, writes regression tests"
1466
+ // ═══════════════════════════════════════════════════════════════
1467
+ export const QaCheckItem: React.FC<{
1468
+ startFrame: number;
1469
+ index: number;
1470
+ label: string;
1471
+ detail: string;
1472
+ }> = ({ startFrame, index, label, detail }) => {
1473
+ const frame = useCurrentFrame();
1474
+ const local = frame - startFrame - index * 26;
1475
+ const op = interpolate(local, [0, 22], [0, 1], { extrapolateRight: "clamp" });
1476
+ const x = interpolate(local, [0, 22], [-30, 0], {
1477
+ extrapolateRight: "clamp",
1478
+ easing: ease.expoOut,
1479
+ });
1480
+ const checkScale = spring({
1481
+ frame: local - 14,
1482
+ fps: 30,
1483
+ config: { damping: 11, stiffness: 160 },
1484
+ from: 0,
1485
+ to: 1,
1486
+ });
1487
+
1488
+ return (
1489
+ <div
1490
+ style={{
1491
+ display: "flex",
1492
+ alignItems: "center",
1493
+ gap: 18,
1494
+ opacity: op,
1495
+ transform: `translateX(${x}px)`,
1496
+ willChange: "transform",
1497
+ padding: "16px 0",
1498
+ borderBottom: `1px solid ${C.hairline}`,
1499
+ }}
1500
+ >
1501
+ <div
1502
+ style={{
1503
+ width: 44,
1504
+ height: 44,
1505
+ borderRadius: 22,
1506
+ background: C.emerald,
1507
+ display: "flex",
1508
+ alignItems: "center",
1509
+ justifyContent: "center",
1510
+ flexShrink: 0,
1511
+ transform: `scale(${checkScale})`,
1512
+ boxShadow: `0 4px 14px ${C.emerald}55`,
1513
+ }}
1514
+ >
1515
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#FFF" strokeWidth="3">
1516
+ <polyline points="5 12 10 17 19 7" />
1517
+ </svg>
1518
+ </div>
1519
+ <div style={{ flex: 1 }}>
1520
+ <div
1521
+ style={{
1522
+ fontFamily: FONT,
1523
+ fontSize: 32,
1524
+ fontWeight: 700,
1525
+ color: C.ink,
1526
+ letterSpacing: "-0.025em",
1527
+ lineHeight: 1.1,
1528
+ }}
1529
+ >
1530
+ {label}
1531
+ </div>
1532
+ <div
1533
+ style={{
1534
+ fontFamily: MONO,
1535
+ fontSize: 16,
1536
+ color: C.inkDim,
1537
+ letterSpacing: "0.04em",
1538
+ marginTop: 2,
1539
+ }}
1540
+ >
1541
+ {detail}
1542
+ </div>
1543
+ </div>
1544
+ </div>
1545
+ );
1546
+ };
1547
+
1548
+ const QaScene: React.FC = () => {
1549
+ const frame = useCurrentFrame();
1550
+ const local = frame - BEAT.qa;
1551
+
1552
+ // Browser frame slides up
1553
+ const browserOp = interpolate(local, [50, 80], [0, 1], { extrapolateRight: "clamp" });
1554
+ const browserY = interpolate(local, [50, 90], [40, 0], {
1555
+ extrapolateRight: "clamp",
1556
+ easing: ease.expoOut,
1557
+ });
1558
+
1559
+ // Cursor moves around
1560
+ const cursorX = interpolate(local, [80, 130, 180, 230, 280], [200, 600, 280, 740, 420], {
1561
+ extrapolateLeft: "clamp",
1562
+ extrapolateRight: "clamp",
1563
+ easing: ease.inOut,
1564
+ });
1565
+ const cursorY = interpolate(local, [80, 130, 180, 230, 280], [240, 180, 320, 260, 220], {
1566
+ extrapolateLeft: "clamp",
1567
+ extrapolateRight: "clamp",
1568
+ easing: ease.inOut,
1569
+ });
1570
+
1571
+ // Click pulse at certain frames
1572
+ const clickPulse = (t: number) => {
1573
+ const d = local - t;
1574
+ if (d < 0 || d > 30) return 0;
1575
+ return interpolate(d, [0, 30], [1, 0], { extrapolateRight: "clamp" });
1576
+ };
1577
+ const pulseA = clickPulse(130);
1578
+ const pulseB = clickPulse(230);
1579
+
1580
+ return (
1581
+ <AbsoluteFill>
1582
+ <div
1583
+ style={{
1584
+ position: "absolute",
1585
+ top: SAFE_TOP + 24,
1586
+ left: 0,
1587
+ right: 0,
1588
+ display: "flex",
1589
+ justifyContent: "center",
1590
+ opacity: interpolate(local, [0, 20], [0, 1], { extrapolateRight: "clamp" }),
1591
+ }}
1592
+ >
1593
+ <EyebrowPill dotColor={C.emerald}>06 — /qa</EyebrowPill>
1594
+ </div>
1595
+
1596
+ {/* Headline */}
1597
+ <div style={{ position: "absolute", left: 80, right: 80, top: SAFE_TOP + 110 }}>
1598
+ <StaggeredWords
1599
+ text="Opens a real"
1600
+ startFrame={BEAT.qa + 6}
1601
+ fontSize={84}
1602
+ fontWeight={500}
1603
+ color={C.inkMuted}
1604
+ align="left"
1605
+ />
1606
+ <div style={{ height: 4 }} />
1607
+ <StaggeredWords
1608
+ text="Chrome browser."
1609
+ startFrame={BEAT.qa + 22}
1610
+ fontSize={104}
1611
+ fontWeight={800}
1612
+ color={C.ink}
1613
+ align="left"
1614
+ letterSpacing="-0.045em"
1615
+ />
1616
+ </div>
1617
+
1618
+ {/* Browser mock */}
1619
+ <div
1620
+ style={{
1621
+ position: "absolute",
1622
+ left: 80,
1623
+ right: 80,
1624
+ top: 740,
1625
+ height: 380,
1626
+ opacity: browserOp,
1627
+ transform: `translateY(${browserY}px)`,
1628
+ willChange: "transform",
1629
+ }}
1630
+ >
1631
+ <div
1632
+ style={{
1633
+ ...glassBase,
1634
+ borderRadius: 24,
1635
+ padding: 12,
1636
+ width: "100%",
1637
+ height: "100%",
1638
+ position: "relative",
1639
+ overflow: "hidden",
1640
+ }}
1641
+ >
1642
+ {/* Browser chrome */}
1643
+ <div
1644
+ style={{
1645
+ display: "flex",
1646
+ alignItems: "center",
1647
+ gap: 8,
1648
+ padding: "4px 6px 12px",
1649
+ }}
1650
+ >
1651
+ <div style={{ width: 12, height: 12, borderRadius: 6, background: "#FF5F57" }} />
1652
+ <div style={{ width: 12, height: 12, borderRadius: 6, background: "#FEBC2E" }} />
1653
+ <div style={{ width: 12, height: 12, borderRadius: 6, background: "#28C840" }} />
1654
+ <div
1655
+ style={{
1656
+ marginLeft: 16,
1657
+ ...glassBase,
1658
+ borderRadius: 9999,
1659
+ padding: "6px 14px",
1660
+ fontFamily: MONO,
1661
+ fontSize: 13,
1662
+ color: C.inkSoft,
1663
+ letterSpacing: "0.02em",
1664
+ flex: 1,
1665
+ maxWidth: 460,
1666
+ }}
1667
+ >
1668
+ <span style={{ color: C.iriViolet }}>https://</span>staging.myapp.com
1669
+ </div>
1670
+ </div>
1671
+ {/* Page mock — rows of grey rectangles */}
1672
+ <div
1673
+ style={{
1674
+ display: "grid",
1675
+ gridTemplateColumns: "1fr 1fr 1fr",
1676
+ gap: 14,
1677
+ padding: 16,
1678
+ }}
1679
+ >
1680
+ {[0, 1, 2, 3, 4, 5].map((i) => (
1681
+ <div
1682
+ key={i}
1683
+ style={{
1684
+ height: 78,
1685
+ borderRadius: 12,
1686
+ background:
1687
+ i === 3
1688
+ ? `linear-gradient(135deg, ${C.rose}, ${C.iriRose})`
1689
+ : "rgba(255,255,255,0.6)",
1690
+ border: `1px solid ${C.hairline}`,
1691
+ }}
1692
+ />
1693
+ ))}
1694
+ </div>
1695
+ <div
1696
+ style={{
1697
+ marginTop: 8,
1698
+ padding: 16,
1699
+ display: "flex",
1700
+ alignItems: "center",
1701
+ gap: 10,
1702
+ fontFamily: MONO,
1703
+ fontSize: 14,
1704
+ color: C.inkDim,
1705
+ letterSpacing: "0.05em",
1706
+ }}
1707
+ >
1708
+ <div
1709
+ style={{
1710
+ width: 8,
1711
+ height: 8,
1712
+ borderRadius: 4,
1713
+ background: C.rose,
1714
+ boxShadow: `0 0 10px ${C.rose}`,
1715
+ }}
1716
+ />
1717
+ BUG · element overflows on viewport &lt; 768px
1718
+ </div>
1719
+
1720
+ {/* Click pulses */}
1721
+ {[
1722
+ { x: 600, y: 180, p: pulseA },
1723
+ { x: 740, y: 260, p: pulseB },
1724
+ ].map((c, i) => (
1725
+ <div
1726
+ key={i}
1727
+ style={{
1728
+ position: "absolute",
1729
+ left: c.x - 30,
1730
+ top: c.y - 30,
1731
+ width: 60,
1732
+ height: 60,
1733
+ borderRadius: 30,
1734
+ border: `3px solid ${C.iriViolet}`,
1735
+ opacity: c.p,
1736
+ transform: `scale(${1 + (1 - c.p) * 1.2})`,
1737
+ pointerEvents: "none",
1738
+ }}
1739
+ />
1740
+ ))}
1741
+
1742
+ {/* Cursor */}
1743
+ <div
1744
+ style={{
1745
+ position: "absolute",
1746
+ left: cursorX,
1747
+ top: cursorY,
1748
+ opacity: browserOp,
1749
+ pointerEvents: "none",
1750
+ willChange: "transform",
1751
+ }}
1752
+ >
1753
+ <svg width="24" height="24" viewBox="0 0 24 24">
1754
+ <path
1755
+ d="M3 3 L21 12 L13 14 L11 22 Z"
1756
+ fill={C.ink}
1757
+ stroke="#FFF"
1758
+ strokeWidth="1.5"
1759
+ />
1760
+ </svg>
1761
+ </div>
1762
+ </div>
1763
+ </div>
1764
+
1765
+ {/* Checklist below */}
1766
+ <div
1767
+ style={{
1768
+ position: "absolute",
1769
+ left: 100,
1770
+ right: 100,
1771
+ top: 1180,
1772
+ }}
1773
+ >
1774
+ <QaCheckItem
1775
+ startFrame={BEAT.qa + 130}
1776
+ index={0}
1777
+ label="Bug found"
1778
+ detail="overflow at sm breakpoint"
1779
+ />
1780
+ <QaCheckItem
1781
+ startFrame={BEAT.qa + 130}
1782
+ index={1}
1783
+ label="Bug fixed"
1784
+ detail="atomic commit · 6 lines"
1785
+ />
1786
+ <QaCheckItem
1787
+ startFrame={BEAT.qa + 130}
1788
+ index={2}
1789
+ label="Regression test written"
1790
+ detail="never comes back"
1791
+ />
1792
+ </div>
1793
+ </AbsoluteFill>
1794
+ );
1795
+ };
1796
+
1797
+ // ═══════════════════════════════════════════════════════════════
1798
+ // SCENE 7 — CSO (1620-1860, 8s)
1799
+ // "/cso runs OWASP audit. $10K → free."
1800
+ // ═══════════════════════════════════════════════════════════════
1801
+ export const OwaspRow: React.FC<{
1802
+ index: number;
1803
+ startFrame: number;
1804
+ label: string;
1805
+ status: "ok" | "warn";
1806
+ }> = ({ index, startFrame, label, status }) => {
1807
+ const frame = useCurrentFrame();
1808
+ const local = frame - startFrame - index * 8;
1809
+ const op = interpolate(local, [0, 18], [0, 1], { extrapolateRight: "clamp" });
1810
+ const x = interpolate(local, [0, 22], [-20, 0], {
1811
+ extrapolateRight: "clamp",
1812
+ easing: ease.expoOut,
1813
+ });
1814
+
1815
+ return (
1816
+ <div
1817
+ style={{
1818
+ display: "flex",
1819
+ alignItems: "center",
1820
+ gap: 14,
1821
+ padding: "10px 16px",
1822
+ borderBottom: `1px solid ${C.hairline}`,
1823
+ opacity: op,
1824
+ transform: `translateX(${x}px)`,
1825
+ willChange: "transform",
1826
+ }}
1827
+ >
1828
+ <div
1829
+ style={{
1830
+ fontFamily: MONO,
1831
+ fontSize: 14,
1832
+ color: C.inkDim,
1833
+ width: 40,
1834
+ letterSpacing: "0.04em",
1835
+ }}
1836
+ >
1837
+ {String(index + 1).padStart(2, "0")}
1838
+ </div>
1839
+ <div
1840
+ style={{
1841
+ flex: 1,
1842
+ fontFamily: FONT,
1843
+ fontSize: 22,
1844
+ fontWeight: 600,
1845
+ color: C.ink,
1846
+ letterSpacing: "-0.01em",
1847
+ }}
1848
+ >
1849
+ {label}
1850
+ </div>
1851
+ <div
1852
+ style={{
1853
+ width: 18,
1854
+ height: 18,
1855
+ borderRadius: 9,
1856
+ background: status === "ok" ? C.emerald : C.iriGold,
1857
+ boxShadow: `0 0 12px ${status === "ok" ? C.emerald : C.iriGold}88`,
1858
+ }}
1859
+ />
1860
+ </div>
1861
+ );
1862
+ };
1863
+
1864
+ const CsoScene: React.FC = () => {
1865
+ const frame = useCurrentFrame();
1866
+ const local = frame - BEAT.cso;
1867
+
1868
+ const owaspItems = [
1869
+ { label: "Broken Access Control", status: "ok" as const },
1870
+ { label: "Cryptographic Failures", status: "ok" as const },
1871
+ { label: "Injection", status: "ok" as const },
1872
+ { label: "Insecure Design", status: "warn" as const },
1873
+ { label: "Security Misconfiguration", status: "ok" as const },
1874
+ { label: "Vulnerable Components", status: "ok" as const },
1875
+ { label: "Auth Failures", status: "ok" as const },
1876
+ { label: "Software & Data Integrity", status: "ok" as const },
1877
+ { label: "Logging & Monitoring", status: "warn" as const },
1878
+ { label: "Server-Side Request Forgery", status: "ok" as const },
1879
+ ];
1880
+
1881
+ // Price ticker — $10,000 → $0
1882
+ const priceScale = spring({
1883
+ frame: local - 100,
1884
+ fps: 30,
1885
+ config: { damping: 11, stiffness: 110 },
1886
+ from: 0.7,
1887
+ to: 1,
1888
+ });
1889
+ const priceOp = interpolate(local, [100, 130], [0, 1], { extrapolateRight: "clamp" });
1890
+
1891
+ return (
1892
+ <AbsoluteFill>
1893
+ <div
1894
+ style={{
1895
+ position: "absolute",
1896
+ top: SAFE_TOP + 24,
1897
+ left: 0,
1898
+ right: 0,
1899
+ display: "flex",
1900
+ justifyContent: "center",
1901
+ opacity: interpolate(local, [0, 20], [0, 1], { extrapolateRight: "clamp" }),
1902
+ }}
1903
+ >
1904
+ <EyebrowPill dotColor={C.iriRose}>07 — /cso</EyebrowPill>
1905
+ </div>
1906
+
1907
+ {/* Headline */}
1908
+ <div style={{ position: "absolute", left: 80, right: 80, top: SAFE_TOP + 110 }}>
1909
+ <StaggeredWords
1910
+ text="Full OWASP audit."
1911
+ startFrame={BEAT.cso + 6}
1912
+ fontSize={92}
1913
+ fontWeight={800}
1914
+ color={C.ink}
1915
+ align="left"
1916
+ letterSpacing="-0.04em"
1917
+ highlight="OWASP"
1918
+ highlightColor={C.iriRose}
1919
+ />
1920
+ </div>
1921
+
1922
+ {/* OWASP Top 10 list */}
1923
+ <div
1924
+ style={{
1925
+ position: "absolute",
1926
+ left: 80,
1927
+ right: 80,
1928
+ top: 720,
1929
+ }}
1930
+ >
1931
+ <div
1932
+ style={{
1933
+ ...glassBase,
1934
+ borderRadius: 28,
1935
+ padding: "24px 14px",
1936
+ }}
1937
+ >
1938
+ <div
1939
+ style={{
1940
+ fontFamily: MONO,
1941
+ fontSize: 14,
1942
+ color: C.iriViolet,
1943
+ letterSpacing: "0.18em",
1944
+ textTransform: "uppercase",
1945
+ fontWeight: 600,
1946
+ padding: "0 16px 14px",
1947
+ }}
1948
+ >
1949
+ owasp top 10 · scanning…
1950
+ </div>
1951
+ {owaspItems.map((item, i) => (
1952
+ <OwaspRow
1953
+ key={i}
1954
+ index={i}
1955
+ startFrame={BEAT.cso + 36}
1956
+ label={item.label}
1957
+ status={item.status}
1958
+ />
1959
+ ))}
1960
+ </div>
1961
+ </div>
1962
+
1963
+ {/* Price comparison */}
1964
+ <div
1965
+ style={{
1966
+ position: "absolute",
1967
+ left: 0,
1968
+ right: 0,
1969
+ top: 1340,
1970
+ display: "flex",
1971
+ justifyContent: "center",
1972
+ alignItems: "center",
1973
+ gap: 36,
1974
+ opacity: priceOp,
1975
+ transform: `scale(${priceScale})`,
1976
+ willChange: "transform",
1977
+ }}
1978
+ >
1979
+ <div
1980
+ style={{
1981
+ fontFamily: FONT,
1982
+ fontSize: 92,
1983
+ fontWeight: 700,
1984
+ color: C.inkDim,
1985
+ letterSpacing: "-0.03em",
1986
+ lineHeight: 1,
1987
+ textDecoration: "line-through",
1988
+ textDecorationColor: C.rose,
1989
+ textDecorationThickness: 6,
1990
+ paddingBottom: 6,
1991
+ }}
1992
+ >
1993
+ $10K
1994
+ </div>
1995
+ <svg
1996
+ width="44"
1997
+ height="44"
1998
+ viewBox="0 0 24 24"
1999
+ fill="none"
2000
+ stroke={C.iriViolet}
2001
+ strokeWidth="2.4"
2002
+ style={{ flexShrink: 0 }}
2003
+ >
2004
+ <line x1="3" y1="12" x2="21" y2="12" />
2005
+ <polyline points="13 5 21 12 13 19" />
2006
+ </svg>
2007
+ <div
2008
+ style={{
2009
+ fontFamily: FONT,
2010
+ fontSize: 128,
2011
+ fontWeight: 800,
2012
+ color: C.ink,
2013
+ letterSpacing: "-0.05em",
2014
+ lineHeight: 1,
2015
+ paddingBottom: 14,
2016
+ background: `linear-gradient(135deg, ${C.iriCyan}, ${C.iriViolet}, ${C.iriRose})`,
2017
+ WebkitBackgroundClip: "text",
2018
+ WebkitTextFillColor: "transparent",
2019
+ backgroundClip: "text",
2020
+ }}
2021
+ >
2022
+ free.
2023
+ </div>
2024
+ </div>
2025
+ </AbsoluteFill>
2026
+ );
2027
+ };
2028
+
2029
+ // ═══════════════════════════════════════════════════════════════
2030
+ // SCENE 8 — PARALLEL (1860-2160, 10s)
2031
+ // "10 to 15 sprints in parallel"
2032
+ // ═══════════════════════════════════════════════════════════════
2033
+ export const ParallelTerminal: React.FC<{
2034
+ startFrame: number;
2035
+ index: number;
2036
+ total: number;
2037
+ cmd: string;
2038
+ status: string;
2039
+ accent: string;
2040
+ }> = ({ startFrame, index, total, cmd, status, accent }) => {
2041
+ const frame = useCurrentFrame();
2042
+ const localStart = startFrame + index * 4;
2043
+ const local = frame - localStart;
2044
+ const op = interpolate(local, [0, 22], [0, 1], { extrapolateRight: "clamp" });
2045
+ const scale = spring({
2046
+ frame: local,
2047
+ fps: 30,
2048
+ config: { damping: 13, stiffness: 130 },
2049
+ from: 0.6,
2050
+ to: 1,
2051
+ });
2052
+
2053
+ // Terminal log line ticker — fake activity
2054
+ const lineCount = Math.floor(local / 8) % 6;
2055
+ const lines = [
2056
+ "▸ analyzing dependencies",
2057
+ "▸ patch applied · 14 lines",
2058
+ "▸ test suite green",
2059
+ "▸ regression added",
2060
+ "▸ commit · b6f3a2",
2061
+ "▸ pushing branch",
2062
+ ];
2063
+ const visibleLines = lines.slice(0, Math.max(2, lineCount + 1));
2064
+
2065
+ // Pulse breathing dot
2066
+ const pulse = 0.7 + Math.sin((frame + index * 14) * 0.12) * 0.3;
2067
+
2068
+ return (
2069
+ <div
2070
+ style={{
2071
+ ...glassBase,
2072
+ borderRadius: 18,
2073
+ padding: "12px 14px",
2074
+ opacity: op,
2075
+ transform: `scale(${scale})`,
2076
+ willChange: "transform",
2077
+ height: "100%",
2078
+ display: "flex",
2079
+ flexDirection: "column",
2080
+ gap: 8,
2081
+ overflow: "hidden",
2082
+ }}
2083
+ >
2084
+ <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
2085
+ <div
2086
+ style={{
2087
+ width: 8,
2088
+ height: 8,
2089
+ borderRadius: 4,
2090
+ background: accent,
2091
+ boxShadow: `0 0 ${8 * pulse + 4}px ${accent}`,
2092
+ }}
2093
+ />
2094
+ <div
2095
+ style={{
2096
+ fontFamily: MONO,
2097
+ fontSize: 13,
2098
+ fontWeight: 600,
2099
+ color: accent,
2100
+ letterSpacing: "-0.005em",
2101
+ }}
2102
+ >
2103
+ {cmd}
2104
+ </div>
2105
+ <div
2106
+ style={{
2107
+ marginLeft: "auto",
2108
+ fontFamily: MONO,
2109
+ fontSize: 10,
2110
+ color: C.inkDim,
2111
+ letterSpacing: "0.1em",
2112
+ textTransform: "uppercase",
2113
+ }}
2114
+ >
2115
+ {status}
2116
+ </div>
2117
+ </div>
2118
+ {visibleLines.map((line, i) => (
2119
+ <div
2120
+ key={i}
2121
+ style={{
2122
+ fontFamily: MONO,
2123
+ fontSize: 11,
2124
+ color: i === visibleLines.length - 1 ? C.inkSoft : C.inkDim,
2125
+ letterSpacing: "-0.005em",
2126
+ lineHeight: 1.3,
2127
+ whiteSpace: "nowrap",
2128
+ overflow: "hidden",
2129
+ textOverflow: "ellipsis",
2130
+ }}
2131
+ >
2132
+ {line}
2133
+ </div>
2134
+ ))}
2135
+ </div>
2136
+ );
2137
+ };
2138
+
2139
+ const ParallelScene: React.FC = () => {
2140
+ const frame = useCurrentFrame();
2141
+ const local = frame - BEAT.parallel;
2142
+
2143
+ const sprints = [
2144
+ { cmd: "/qa", status: "running", accent: C.iriCyan },
2145
+ { cmd: "/ship", status: "pushing", accent: C.iriViolet },
2146
+ { cmd: "/cso", status: "scanning", accent: C.iriRose },
2147
+ { cmd: "/review", status: "auto-fix", accent: C.emerald },
2148
+ { cmd: "/investigate", status: "tracing", accent: C.iriGold },
2149
+ { cmd: "/benchmark", status: "profiling", accent: C.iriCyan },
2150
+ { cmd: "/canary", status: "watching", accent: C.iriViolet },
2151
+ { cmd: "/design-html", status: "compiling", accent: C.iriRose },
2152
+ { cmd: "/document-release", status: "writing", accent: C.emerald },
2153
+ { cmd: "/devex-review", status: "auditing", accent: C.iriGold },
2154
+ { cmd: "/land-and-deploy", status: "deploy", accent: C.iriCyan },
2155
+ { cmd: "/retro", status: "summarize", accent: C.iriViolet },
2156
+ ];
2157
+
2158
+ // Big "× 12" callout pops late
2159
+ const calloutOp = interpolate(local, [180, 210], [0, 1], { extrapolateRight: "clamp" });
2160
+ const calloutScale = spring({
2161
+ frame: local - 180,
2162
+ fps: 30,
2163
+ config: { damping: 12, stiffness: 130 },
2164
+ from: 0.7,
2165
+ to: 1,
2166
+ });
2167
+
2168
+ return (
2169
+ <AbsoluteFill>
2170
+ <div
2171
+ style={{
2172
+ position: "absolute",
2173
+ top: SAFE_TOP + 24,
2174
+ left: 0,
2175
+ right: 0,
2176
+ display: "flex",
2177
+ justifyContent: "center",
2178
+ opacity: interpolate(local, [0, 20], [0, 1], { extrapolateRight: "clamp" }),
2179
+ }}
2180
+ >
2181
+ <EyebrowPill dotColor={C.iriCyan}>08 — in parallel</EyebrowPill>
2182
+ </div>
2183
+
2184
+ {/* Headline */}
2185
+ <div style={{ position: "absolute", left: 80, right: 80, top: SAFE_TOP + 110 }}>
2186
+ <StaggeredWords
2187
+ text="All at the"
2188
+ startFrame={BEAT.parallel + 6}
2189
+ fontSize={84}
2190
+ fontWeight={500}
2191
+ color={C.inkMuted}
2192
+ align="left"
2193
+ />
2194
+ <div style={{ height: 4 }} />
2195
+ <StaggeredWords
2196
+ text="same time."
2197
+ startFrame={BEAT.parallel + 22}
2198
+ fontSize={132}
2199
+ fontWeight={800}
2200
+ color={C.ink}
2201
+ align="left"
2202
+ letterSpacing="-0.05em"
2203
+ highlight="same"
2204
+ highlightColor={C.iriCyan}
2205
+ />
2206
+ </div>
2207
+
2208
+ {/* 12 parallel terminals — 3-col grid, 4 rows */}
2209
+ <div
2210
+ style={{
2211
+ position: "absolute",
2212
+ left: 60,
2213
+ right: 60,
2214
+ top: 760,
2215
+ height: 640,
2216
+ display: "grid",
2217
+ gridTemplateColumns: "repeat(3, 1fr)",
2218
+ gridTemplateRows: "repeat(4, 1fr)",
2219
+ gap: 14,
2220
+ }}
2221
+ >
2222
+ {sprints.map((s, i) => (
2223
+ <ParallelTerminal
2224
+ key={i}
2225
+ startFrame={BEAT.parallel + 30}
2226
+ index={i}
2227
+ total={sprints.length}
2228
+ cmd={s.cmd}
2229
+ status={s.status}
2230
+ accent={s.accent}
2231
+ />
2232
+ ))}
2233
+ </div>
2234
+
2235
+ {/* Callout */}
2236
+ <div
2237
+ style={{
2238
+ position: "absolute",
2239
+ left: 0,
2240
+ right: 0,
2241
+ top: 1430,
2242
+ display: "flex",
2243
+ justifyContent: "center",
2244
+ opacity: calloutOp,
2245
+ transform: `scale(${calloutScale})`,
2246
+ willChange: "transform",
2247
+ }}
2248
+ >
2249
+ <div
2250
+ style={{
2251
+ ...glassBase,
2252
+ borderRadius: 9999,
2253
+ padding: "16px 32px",
2254
+ display: "inline-flex",
2255
+ alignItems: "center",
2256
+ gap: 14,
2257
+ fontFamily: FONT,
2258
+ fontSize: 38,
2259
+ fontWeight: 700,
2260
+ color: C.ink,
2261
+ letterSpacing: "-0.025em",
2262
+ }}
2263
+ >
2264
+ <span style={{ color: C.iriViolet, fontFamily: MONO, fontWeight: 800 }}>10 → 15</span>
2265
+ sprints simultaneously
2266
+ </div>
2267
+ </div>
2268
+ </AbsoluteFill>
2269
+ );
2270
+ };
2271
+
2272
+ // ═══════════════════════════════════════════════════════════════
2273
+ // SCENE 9 — GARRY (2160-2520, 12s)
2274
+ // "Built by Garry Tan. 800× faster than 2013."
2275
+ // ═══════════════════════════════════════════════════════════════
2276
+ export const PortfolioPill: React.FC<{
2277
+ index: number;
2278
+ startFrame: number;
2279
+ name: string;
2280
+ color: string;
2281
+ }> = ({ index, startFrame, name, color }) => {
2282
+ const frame = useCurrentFrame();
2283
+ const local = frame - startFrame - index * 7;
2284
+ const op = interpolate(local, [0, 22], [0, 1], { extrapolateRight: "clamp" });
2285
+ const scale = spring({
2286
+ frame: local,
2287
+ fps: 30,
2288
+ config: { damping: 11, stiffness: 130 },
2289
+ from: 0.7,
2290
+ to: 1,
2291
+ });
2292
+ const float = Math.sin((frame + index * 24) * 0.04) * 5;
2293
+
2294
+ return (
2295
+ <div
2296
+ style={{
2297
+ ...glassBase,
2298
+ borderRadius: 9999,
2299
+ padding: "12px 22px",
2300
+ display: "inline-flex",
2301
+ alignItems: "center",
2302
+ gap: 10,
2303
+ opacity: op,
2304
+ transform: `scale(${scale}) translateY(${float}px)`,
2305
+ willChange: "transform",
2306
+ }}
2307
+ >
2308
+ <div
2309
+ style={{
2310
+ width: 12,
2311
+ height: 12,
2312
+ borderRadius: 6,
2313
+ background: color,
2314
+ boxShadow: `0 0 10px ${color}`,
2315
+ }}
2316
+ />
2317
+ <div
2318
+ style={{
2319
+ fontFamily: FONT,
2320
+ fontSize: 22,
2321
+ fontWeight: 600,
2322
+ color: C.ink,
2323
+ letterSpacing: "-0.015em",
2324
+ }}
2325
+ >
2326
+ {name}
2327
+ </div>
2328
+ </div>
2329
+ );
2330
+ };
2331
+
2332
+ const GarryScene: React.FC = () => {
2333
+ const frame = useCurrentFrame();
2334
+ const local = frame - BEAT.garry;
2335
+
2336
+ // Portrait scale-pop
2337
+ const portraitScale = spring({
2338
+ frame: local - 30,
2339
+ fps: 30,
2340
+ config: { damping: 13, stiffness: 110 },
2341
+ from: 0.7,
2342
+ to: 1,
2343
+ });
2344
+ const portraitOp = interpolate(local, [30, 60], [0, 1], { extrapolateRight: "clamp" });
2345
+
2346
+ // Counter — 1× → 800×
2347
+ const counterOp = interpolate(local, [180, 210], [0, 1], { extrapolateRight: "clamp" });
2348
+ const counterScale = spring({
2349
+ frame: local - 180,
2350
+ fps: 30,
2351
+ config: { damping: 12, stiffness: 110 },
2352
+ from: 0.6,
2353
+ to: 1,
2354
+ });
2355
+
2356
+ return (
2357
+ <AbsoluteFill>
2358
+ <div
2359
+ style={{
2360
+ position: "absolute",
2361
+ top: SAFE_TOP + 24,
2362
+ left: 0,
2363
+ right: 0,
2364
+ display: "flex",
2365
+ justifyContent: "center",
2366
+ opacity: interpolate(local, [0, 20], [0, 1], { extrapolateRight: "clamp" }),
2367
+ }}
2368
+ >
2369
+ <EyebrowPill dotColor={C.ycOrange}>09 — built by</EyebrowPill>
2370
+ </div>
2371
+
2372
+ {/* Portrait — center, with iridescent ring */}
2373
+ <div
2374
+ style={{
2375
+ position: "absolute",
2376
+ left: "50%",
2377
+ top: SAFE_TOP + 100,
2378
+ transform: `translateX(-50%) scale(${portraitScale})`,
2379
+ opacity: portraitOp,
2380
+ willChange: "transform",
2381
+ }}
2382
+ >
2383
+ <div style={{ position: "relative", width: 360, height: 360 }}>
2384
+ <IridescentRing size={360} thickness={6} speed={0.8} />
2385
+ <div
2386
+ style={{
2387
+ position: "absolute",
2388
+ inset: 6,
2389
+ borderRadius: "50%",
2390
+ overflow: "hidden",
2391
+ background: C.bg,
2392
+ boxShadow: "inset 0 4px 20px rgba(0,0,0,0.15)",
2393
+ }}
2394
+ >
2395
+ <Img
2396
+ src={staticFile("garrytan-image.png")}
2397
+ style={{
2398
+ width: "100%",
2399
+ height: "100%",
2400
+ objectFit: "cover",
2401
+ objectPosition: "center 30%",
2402
+ }}
2403
+ />
2404
+ </div>
2405
+ </div>
2406
+ </div>
2407
+
2408
+ {/* Name + title */}
2409
+ <div
2410
+ style={{
2411
+ position: "absolute",
2412
+ left: 0,
2413
+ right: 0,
2414
+ top: SAFE_TOP + 510,
2415
+ textAlign: "center",
2416
+ opacity: interpolate(local, [60, 90], [0, 1], { extrapolateRight: "clamp" }),
2417
+ transform: `translateY(${interpolate(local, [60, 90], [16, 0], { extrapolateRight: "clamp", easing: ease.power3Out })}px)`,
2418
+ }}
2419
+ >
2420
+ <div
2421
+ style={{
2422
+ fontFamily: FONT,
2423
+ fontSize: 76,
2424
+ fontWeight: 800,
2425
+ color: C.ink,
2426
+ letterSpacing: "-0.04em",
2427
+ lineHeight: 1,
2428
+ }}
2429
+ >
2430
+ Garry Tan
2431
+ </div>
2432
+ <div
2433
+ style={{
2434
+ fontFamily: MONO,
2435
+ fontSize: 18,
2436
+ color: C.inkMuted,
2437
+ letterSpacing: "0.18em",
2438
+ textTransform: "uppercase",
2439
+ fontWeight: 600,
2440
+ marginTop: 10,
2441
+ }}
2442
+ >
2443
+ president · y combinator
2444
+ </div>
2445
+ </div>
2446
+
2447
+ {/* Portfolio pills — Coinbase, Stripe, Airbnb */}
2448
+ <div
2449
+ style={{
2450
+ position: "absolute",
2451
+ left: 0,
2452
+ right: 0,
2453
+ top: SAFE_TOP + 660,
2454
+ display: "flex",
2455
+ justifyContent: "center",
2456
+ gap: 14,
2457
+ flexWrap: "wrap",
2458
+ padding: "0 60px",
2459
+ }}
2460
+ >
2461
+ <PortfolioPill index={0} startFrame={BEAT.garry + 100} name="Coinbase" color="#0052FF" />
2462
+ <PortfolioPill index={1} startFrame={BEAT.garry + 100} name="Stripe" color="#635BFF" />
2463
+ <PortfolioPill index={2} startFrame={BEAT.garry + 100} name="Airbnb" color="#FF5A5F" />
2464
+ <PortfolioPill index={3} startFrame={BEAT.garry + 100} name="Instacart" color="#43B02A" />
2465
+ </div>
2466
+
2467
+ {/* 1× → 800× counter */}
2468
+ <div
2469
+ style={{
2470
+ position: "absolute",
2471
+ left: 0,
2472
+ right: 0,
2473
+ top: 1280,
2474
+ display: "flex",
2475
+ alignItems: "baseline",
2476
+ justifyContent: "center",
2477
+ gap: 18,
2478
+ opacity: counterOp,
2479
+ transform: `scale(${counterScale})`,
2480
+ willChange: "transform",
2481
+ }}
2482
+ >
2483
+ <div
2484
+ style={{
2485
+ display: "flex",
2486
+ flexDirection: "column",
2487
+ alignItems: "center",
2488
+ gap: 4,
2489
+ }}
2490
+ >
2491
+ <div
2492
+ style={{
2493
+ fontFamily: MONO,
2494
+ fontSize: 14,
2495
+ color: C.inkDim,
2496
+ letterSpacing: "0.18em",
2497
+ textTransform: "uppercase",
2498
+ fontWeight: 600,
2499
+ }}
2500
+ >
2501
+ 2013
2502
+ </div>
2503
+ <div
2504
+ style={{
2505
+ fontFamily: MONO,
2506
+ fontSize: 64,
2507
+ fontWeight: 700,
2508
+ color: C.inkMuted,
2509
+ letterSpacing: "-0.04em",
2510
+ textDecoration: "line-through",
2511
+ textDecorationColor: C.inkDim,
2512
+ textDecorationThickness: 3,
2513
+ }}
2514
+ >
2515
+
2516
+ </div>
2517
+ </div>
2518
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke={C.iriViolet} strokeWidth="2.4">
2519
+ <line x1="3" y1="12" x2="21" y2="12" />
2520
+ <polyline points="13 5 21 12 13 19" />
2521
+ </svg>
2522
+ <div
2523
+ style={{
2524
+ display: "flex",
2525
+ flexDirection: "column",
2526
+ alignItems: "center",
2527
+ gap: 4,
2528
+ }}
2529
+ >
2530
+ <div
2531
+ style={{
2532
+ fontFamily: MONO,
2533
+ fontSize: 14,
2534
+ color: C.iriViolet,
2535
+ letterSpacing: "0.18em",
2536
+ textTransform: "uppercase",
2537
+ fontWeight: 600,
2538
+ }}
2539
+ >
2540
+ 2026
2541
+ </div>
2542
+ <div
2543
+ style={{
2544
+ fontFamily: MONO,
2545
+ fontSize: 168,
2546
+ fontWeight: 800,
2547
+ color: C.ink,
2548
+ letterSpacing: "-0.06em",
2549
+ lineHeight: 0.9,
2550
+ fontVariantNumeric: "tabular-nums",
2551
+ background: `linear-gradient(135deg, ${C.iriCyan}, ${C.iriViolet}, ${C.iriRose})`,
2552
+ WebkitBackgroundClip: "text",
2553
+ WebkitTextFillColor: "transparent",
2554
+ backgroundClip: "text",
2555
+ }}
2556
+ >
2557
+ <Counter
2558
+ from={1}
2559
+ to={800}
2560
+ startFrame={BEAT.garry + 180}
2561
+ duration={90}
2562
+ format={(n) => `${Math.round(n)}×`}
2563
+ />
2564
+ </div>
2565
+ </div>
2566
+ </div>
2567
+
2568
+ {/* Caption */}
2569
+ <div
2570
+ style={{
2571
+ position: "absolute",
2572
+ left: 80,
2573
+ right: 80,
2574
+ top: 1480,
2575
+ textAlign: "center",
2576
+ opacity: interpolate(local, [240, 280], [0, 1], { extrapolateRight: "clamp" }),
2577
+ }}
2578
+ >
2579
+ <div
2580
+ style={{
2581
+ fontFamily: FONT,
2582
+ fontSize: 30,
2583
+ fontWeight: 500,
2584
+ color: C.inkMuted,
2585
+ letterSpacing: "-0.01em",
2586
+ }}
2587
+ >
2588
+ Same builder. Different tooling.
2589
+ </div>
2590
+ </div>
2591
+ </AbsoluteFill>
2592
+ );
2593
+ };
2594
+
2595
+ // ═══════════════════════════════════════════════════════════════
2596
+ // SCENE 10 — CTA (2520-2820, 10s)
2597
+ // "Free. MIT. 85,000 stars. Pinned comment."
2598
+ // ═══════════════════════════════════════════════════════════════
2599
+
2600
+ // Concentric rings that pulse out from center
2601
+ const CtaSonar: React.FC = () => {
2602
+ const frame = useCurrentFrame();
2603
+ const local = frame - BEAT.cta;
2604
+ const rings = [0, 22, 44, 66, 88, 110];
2605
+ return (
2606
+ <AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
2607
+ {rings.map((birth, i) => {
2608
+ const lf = local - birth;
2609
+ if (lf < 0) return null;
2610
+ const cycle = lf % 110;
2611
+ const scale = interpolate(cycle, [0, 110], [0.05, 1.2], {
2612
+ extrapolateLeft: "clamp",
2613
+ extrapolateRight: "clamp",
2614
+ easing: ease.expoOut,
2615
+ });
2616
+ const op = interpolate(cycle, [0, 30, 110], [0, 0.5, 0], {
2617
+ extrapolateLeft: "clamp",
2618
+ extrapolateRight: "clamp",
2619
+ });
2620
+ return (
2621
+ <div
2622
+ key={i}
2623
+ style={{
2624
+ position: "absolute",
2625
+ width: 1500,
2626
+ height: 1500,
2627
+ borderRadius: "50%",
2628
+ border: `2px solid ${i % 2 === 0 ? C.iriGold : C.iriViolet}`,
2629
+ transform: `scale(${scale})`,
2630
+ opacity: op,
2631
+ willChange: "transform, opacity",
2632
+ pointerEvents: "none",
2633
+ }}
2634
+ />
2635
+ );
2636
+ })}
2637
+ </AbsoluteFill>
2638
+ );
2639
+ };
2640
+
2641
+ // Floating glass star shards that drift and pulse
2642
+ const FloatingStars: React.FC = () => {
2643
+ const frame = useCurrentFrame();
2644
+ const local = frame - BEAT.cta;
2645
+ const shards = [
2646
+ { x: 90, y: 320, size: 56, delay: 6, rot: -10, color: C.iriGold },
2647
+ { x: 920, y: 280, size: 48, delay: 14, rot: 14, color: C.iriCyan },
2648
+ { x: 70, y: 760, size: 64, delay: 22, rot: 6, color: C.iriViolet },
2649
+ { x: 940, y: 820, size: 54, delay: 30, rot: -8, color: C.iriRose },
2650
+ { x: 140, y: 1180, size: 44, delay: 38, rot: 16, color: C.iriGold },
2651
+ { x: 880, y: 1240, size: 60, delay: 46, rot: -4, color: C.iriCyan },
2652
+ { x: 200, y: 540, size: 36, delay: 18, rot: 8, color: C.iriRose },
2653
+ { x: 820, y: 580, size: 40, delay: 26, rot: -12, color: C.iriViolet },
2654
+ ];
2655
+ return (
2656
+ <>
2657
+ {shards.map((s, i) => {
2658
+ const lf = local - s.delay;
2659
+ const op = interpolate(lf, [0, 30], [0, 1], {
2660
+ extrapolateLeft: "clamp",
2661
+ extrapolateRight: "clamp",
2662
+ easing: ease.expoOut,
2663
+ });
2664
+ const float = Math.sin((frame + i * 26) * 0.045) * 12;
2665
+ const drift = Math.cos((frame + i * 19) * 0.035) * 8;
2666
+ const sc = spring({
2667
+ frame: lf,
2668
+ fps: 30,
2669
+ config: { damping: 12, stiffness: 110 },
2670
+ from: 0.4,
2671
+ to: 1,
2672
+ });
2673
+ const spin = (frame + i * 30) * 0.4;
2674
+ return (
2675
+ <div
2676
+ key={i}
2677
+ style={{
2678
+ position: "absolute",
2679
+ left: s.x + drift,
2680
+ top: s.y + float,
2681
+ width: s.size,
2682
+ height: s.size,
2683
+ transform: `rotate(${s.rot + spin}deg) scale(${sc})`,
2684
+ opacity: op * 0.85,
2685
+ willChange: "transform, opacity",
2686
+ pointerEvents: "none",
2687
+ filter: `drop-shadow(0 0 ${s.size * 0.4}px ${s.color}88)`,
2688
+ }}
2689
+ >
2690
+ <svg width={s.size} height={s.size} viewBox="0 0 24 24" fill={s.color}>
2691
+ <polygon points="12 2 15 8.5 22 9.3 17 14 18.5 21 12 17.5 5.5 21 7 14 2 9.3 9 8.5" />
2692
+ </svg>
2693
+ </div>
2694
+ );
2695
+ })}
2696
+ </>
2697
+ );
2698
+ };
2699
+
2700
+ // Diagonal iridescent light sweep
2701
+ const CtaLightSweep: React.FC<{ delay: number; angle: number }> = ({ delay, angle }) => {
2702
+ const frame = useCurrentFrame();
2703
+ const local = frame - BEAT.cta - delay;
2704
+ const progress = interpolate(local, [0, 80], [-0.3, 1.3], {
2705
+ extrapolateLeft: "clamp",
2706
+ extrapolateRight: "clamp",
2707
+ easing: ease.expoOut,
2708
+ });
2709
+ const op = interpolate(local, [0, 14, 60, 80], [0, 0.55, 0.55, 0], {
2710
+ extrapolateLeft: "clamp",
2711
+ extrapolateRight: "clamp",
2712
+ });
2713
+ return (
2714
+ <div
2715
+ style={{
2716
+ position: "absolute",
2717
+ top: "50%",
2718
+ left: "50%",
2719
+ width: 2400,
2720
+ height: 90,
2721
+ background: `linear-gradient(90deg, transparent 0%, ${C.iriGold}aa 30%, ${C.iriViolet}cc 50%, ${C.iriRose}aa 70%, transparent 100%)`,
2722
+ filter: "blur(22px)",
2723
+ transform: `translate(-50%, -50%) rotate(${angle}deg) translateX(${(progress - 0.5) * 1700}px)`,
2724
+ opacity: op,
2725
+ willChange: "transform, opacity",
2726
+ pointerEvents: "none",
2727
+ }}
2728
+ />
2729
+ );
2730
+ };
2731
+
2732
+ export const StarBurst: React.FC = () => {
2733
+ const frame = useCurrentFrame();
2734
+ const local = frame - BEAT.cta;
2735
+ const stars = Array.from({ length: 24 }, (_, i) => {
2736
+ const angle = (i / 24) * Math.PI * 2 + 0.31;
2737
+ const distance = 240 + (i % 5) * 50;
2738
+ const delay = (i * 1.2) % 26;
2739
+ const lf = local - 60 - delay;
2740
+ const burst = interpolate(lf, [0, 50], [0, 1], {
2741
+ extrapolateLeft: "clamp",
2742
+ extrapolateRight: "clamp",
2743
+ easing: ease.expoOut,
2744
+ });
2745
+ const drift = Math.sin((frame + i * 12) * 0.05) * 6;
2746
+ const x = Math.cos(angle) * distance * burst + drift;
2747
+ const y = Math.sin(angle) * distance * burst + drift * 0.6;
2748
+ const op = interpolate(lf, [0, 16, 100, 180], [0, 0.85, 0.5, 0.25], {
2749
+ extrapolateLeft: "clamp",
2750
+ extrapolateRight: "clamp",
2751
+ });
2752
+ const size = 10 + (i % 3) * 5;
2753
+ return { x, y, op, size, key: i };
2754
+ });
2755
+ return (
2756
+ <>
2757
+ {stars.map((s) => (
2758
+ <div
2759
+ key={s.key}
2760
+ style={{
2761
+ position: "absolute",
2762
+ left: "50%",
2763
+ top: "50%",
2764
+ width: s.size,
2765
+ height: s.size,
2766
+ transform: `translate(${s.x - s.size / 2}px, ${s.y - s.size / 2}px)`,
2767
+ opacity: s.op,
2768
+ willChange: "transform, opacity",
2769
+ pointerEvents: "none",
2770
+ filter: `drop-shadow(0 0 ${s.size}px ${C.iriGold})`,
2771
+ }}
2772
+ >
2773
+ <svg width={s.size} height={s.size} viewBox="0 0 24 24" fill={C.iriGold}>
2774
+ <polygon points="12 2 15 8.5 22 9.3 17 14 18.5 21 12 17.5 5.5 21 7 14 2 9.3 9 8.5" />
2775
+ </svg>
2776
+ </div>
2777
+ ))}
2778
+ </>
2779
+ );
2780
+ };
2781
+
2782
+ const CtaScene: React.FC = () => {
2783
+ const frame = useCurrentFrame();
2784
+ const local = frame - BEAT.cta;
2785
+
2786
+ // Star count scale-pop
2787
+ const numScale = spring({
2788
+ frame: local - 20,
2789
+ fps: 30,
2790
+ config: { damping: 12, stiffness: 110 },
2791
+ from: 0.5,
2792
+ to: 1,
2793
+ });
2794
+ const numOp = interpolate(local, [20, 50], [0, 1], { extrapolateRight: "clamp" });
2795
+
2796
+ // Pinned-comment arrow bobs
2797
+ const arrowBob = Math.sin(frame * 0.12) * 14;
2798
+
2799
+ return (
2800
+ <AbsoluteFill>
2801
+ <div
2802
+ style={{
2803
+ position: "absolute",
2804
+ top: SAFE_TOP + 24,
2805
+ left: 0,
2806
+ right: 0,
2807
+ display: "flex",
2808
+ justifyContent: "center",
2809
+ opacity: interpolate(local, [0, 20], [0, 1], { extrapolateRight: "clamp" }),
2810
+ }}
2811
+ >
2812
+ <EyebrowPill dotColor={C.iriGold}>10 — free</EyebrowPill>
2813
+ </div>
2814
+
2815
+ {/* Motion-art layers — sonar rings + light sweeps + floating stars + center burst */}
2816
+ <CtaSonar />
2817
+ <CtaLightSweep delay={0} angle={-16} />
2818
+ <CtaLightSweep delay={36} angle={20} />
2819
+ <CtaLightSweep delay={80} angle={-10} />
2820
+ <FloatingStars />
2821
+ <AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
2822
+ <div style={{ position: "relative", width: 1, height: 1 }}>
2823
+ <StarBurst />
2824
+ </div>
2825
+ </AbsoluteFill>
2826
+
2827
+ {/* Star count — sized to fit 1080 width with side padding */}
2828
+ <div
2829
+ style={{
2830
+ position: "absolute",
2831
+ left: 40,
2832
+ right: 40,
2833
+ top: SAFE_TOP + 180,
2834
+ textAlign: "center",
2835
+ opacity: numOp,
2836
+ transform: `scale(${numScale})`,
2837
+ willChange: "transform",
2838
+ }}
2839
+ >
2840
+ <div
2841
+ style={{
2842
+ fontFamily: MONO,
2843
+ fontSize: 256,
2844
+ fontWeight: 700,
2845
+ color: C.ink,
2846
+ letterSpacing: "-0.075em",
2847
+ lineHeight: 0.85,
2848
+ fontVariantNumeric: "tabular-nums",
2849
+ background: `linear-gradient(135deg, ${C.iriCyan} 0%, ${C.iriViolet} 50%, ${C.iriRose} 100%)`,
2850
+ WebkitBackgroundClip: "text",
2851
+ WebkitTextFillColor: "transparent",
2852
+ backgroundClip: "text",
2853
+ whiteSpace: "nowrap",
2854
+ }}
2855
+ >
2856
+ <Counter
2857
+ from={0}
2858
+ to={85000}
2859
+ startFrame={BEAT.cta + 20}
2860
+ duration={90}
2861
+ format={(n) => Math.round(n).toLocaleString()}
2862
+ />
2863
+ </div>
2864
+ <div
2865
+ style={{
2866
+ display: "inline-flex",
2867
+ alignItems: "center",
2868
+ gap: 14,
2869
+ marginTop: 18,
2870
+ fontFamily: MONO,
2871
+ fontSize: 28,
2872
+ fontWeight: 600,
2873
+ color: C.inkSoft,
2874
+ letterSpacing: "0.18em",
2875
+ textTransform: "uppercase",
2876
+ }}
2877
+ >
2878
+ <svg width="28" height="28" viewBox="0 0 24 24" fill={C.iriGold}>
2879
+ <polygon points="12 2 15 8.5 22 9.3 17 14 18.5 21 12 17.5 5.5 21 7 14 2 9.3 9 8.5" />
2880
+ </svg>
2881
+ stars on github
2882
+ </div>
2883
+ </div>
2884
+
2885
+ {/* Free / MIT / Open Source pills */}
2886
+ <div
2887
+ style={{
2888
+ position: "absolute",
2889
+ left: 0,
2890
+ right: 0,
2891
+ top: SAFE_TOP + 700,
2892
+ display: "flex",
2893
+ justifyContent: "center",
2894
+ gap: 14,
2895
+ opacity: interpolate(local, [80, 120], [0, 1], { extrapolateRight: "clamp" }),
2896
+ transform: `translateY(${interpolate(local, [80, 120], [20, 0], { extrapolateRight: "clamp", easing: ease.power3Out })}px)`,
2897
+ }}
2898
+ >
2899
+ {[
2900
+ { label: "FREE", color: C.emerald },
2901
+ { label: "MIT", color: C.iriViolet },
2902
+ { label: "OPEN SOURCE", color: C.iriCyan },
2903
+ ].map((p, i) => (
2904
+ <div
2905
+ key={i}
2906
+ style={{
2907
+ ...glassBase,
2908
+ borderRadius: 9999,
2909
+ padding: "18px 30px",
2910
+ display: "inline-flex",
2911
+ alignItems: "center",
2912
+ gap: 12,
2913
+ fontFamily: MONO,
2914
+ fontSize: 22,
2915
+ fontWeight: 700,
2916
+ color: C.ink,
2917
+ letterSpacing: "0.15em",
2918
+ }}
2919
+ >
2920
+ <div
2921
+ style={{
2922
+ width: 10,
2923
+ height: 10,
2924
+ borderRadius: 5,
2925
+ background: p.color,
2926
+ boxShadow: `0 0 12px ${p.color}`,
2927
+ }}
2928
+ />
2929
+ {p.label}
2930
+ </div>
2931
+ ))}
2932
+ </div>
2933
+
2934
+ {/* Pinned comment CTA */}
2935
+ <div
2936
+ style={{
2937
+ position: "absolute",
2938
+ left: 0,
2939
+ right: 0,
2940
+ top: 1380,
2941
+ textAlign: "center",
2942
+ opacity: interpolate(local, [140, 180], [0, 1], { extrapolateRight: "clamp" }),
2943
+ transform: `translateY(${arrowBob}px)`,
2944
+ willChange: "transform",
2945
+ }}
2946
+ >
2947
+ <div
2948
+ style={{
2949
+ fontFamily: FONT,
2950
+ fontSize: 52,
2951
+ fontWeight: 700,
2952
+ color: C.ink,
2953
+ letterSpacing: "-0.03em",
2954
+ lineHeight: 1.05,
2955
+ }}
2956
+ >
2957
+ link in pinned comment
2958
+ </div>
2959
+ <div style={{ marginTop: 18, display: "flex", justifyContent: "center" }}>
2960
+ <svg width="56" height="56" viewBox="0 0 24 24" fill="none" stroke={C.iriViolet} strokeWidth="2.4">
2961
+ <line x1="12" y1="5" x2="12" y2="19" />
2962
+ <polyline points="19 12 12 19 5 12" />
2963
+ </svg>
2964
+ </div>
2965
+ </div>
2966
+ </AbsoluteFill>
2967
+ );
2968
+ };
2969
+
2970
+ // ═══════════════════════════════════════════════════════════════
2971
+ // MAIN — orchestrate scenes with audio
2972
+ // ═══════════════════════════════════════════════════════════════
2973
+ export const GstackReel: React.FC = () => {
2974
+ const { width, height } = useVideoConfig();
2975
+ const frame = useCurrentFrame();
2976
+
2977
+ return (
2978
+ <AbsoluteFill
2979
+ style={{
2980
+ background: `radial-gradient(ellipse at 30% 20%, ${C.bgWarm} 0%, ${C.bg} 50%, ${C.bgCool} 100%)`,
2981
+ width,
2982
+ height,
2983
+ overflow: "hidden",
2984
+ }}
2985
+ >
2986
+ {/* Perpetual caustic blobs — drift across entire reel */}
2987
+ <CausticBlobs />
2988
+
2989
+ {/* Subtle hairline grid for depth */}
2990
+ <HairlineGrid opacity={0.04} />
2991
+
2992
+ {/* Film-grain noise (fixed, pointer-events none) */}
2993
+ <AbsoluteFill
2994
+ style={{
2995
+ backgroundImage:
2996
+ "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E\")",
2997
+ opacity: 0.06,
2998
+ pointerEvents: "none",
2999
+ mixBlendMode: "multiply",
3000
+ }}
3001
+ />
3002
+
3003
+ {/* Scenes — conditional render so useCurrentFrame() returns GLOBAL frame
3004
+ inside each scene, matching our `local = frame - BEAT.x` math. */}
3005
+ {frame >= BEAT.hook && frame < BEAT.reveal && <HookScene />}
3006
+ {frame >= BEAT.reveal && frame < BEAT.pitch && <RevealScene />}
3007
+ {frame >= BEAT.pitch && frame < BEAT.officeHours && <PitchScene />}
3008
+ {frame >= BEAT.officeHours && frame < BEAT.reframe && <OfficeHoursScene />}
3009
+ {frame >= BEAT.reframe && frame < BEAT.qa && <ReframeScene />}
3010
+ {frame >= BEAT.qa && frame < BEAT.cso && <QaScene />}
3011
+ {frame >= BEAT.cso && frame < BEAT.parallel && <CsoScene />}
3012
+ {frame >= BEAT.parallel && frame < BEAT.garry && <ParallelScene />}
3013
+ {frame >= BEAT.garry && frame < BEAT.cta && <GarryScene />}
3014
+ {frame >= BEAT.cta && frame < BEAT.end && <CtaScene />}
3015
+
3016
+ {/* Audio */}
3017
+ {/* REFERENCE-STRIP: voiceover tag removed — bring your own voiceover */}
3018
+ </AbsoluteFill>
3019
+ );
3020
+ };