@devinilabs/reelstack 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/LICENSE +128 -0
  2. package/README.md +125 -0
  3. package/cli/beats.js +124 -0
  4. package/cli/bootstrap.js +124 -0
  5. package/cli/capture.js +34 -0
  6. package/cli/direction.js +114 -0
  7. package/cli/icons.js +49 -0
  8. package/cli/index.js +101 -0
  9. package/cli/init.js +253 -0
  10. package/cli/license.js +168 -0
  11. package/cli/lint.js +865 -0
  12. package/cli/preview.js +59 -0
  13. package/cli/render.js +404 -0
  14. package/cli/scaffold.js +239 -0
  15. package/cli/smoke.js +76 -0
  16. package/cli/update.js +26 -0
  17. package/cli/utils.js +184 -0
  18. package/docs/buyers-guide.md +220 -0
  19. package/docs/design-discipline.md +130 -0
  20. package/docs/family-galleries/dark.md +95 -0
  21. package/docs/family-galleries/forbidden.md +78 -0
  22. package/docs/family-galleries/glass.md +98 -0
  23. package/docs/family-galleries/paper.md +82 -0
  24. package/docs/family-galleries/warm.md +86 -0
  25. package/docs/superpowers/plans/2026-05-09-reelstack-init-readiness-gate.md +1166 -0
  26. package/docs/superpowers/specs/2026-05-09-reelstack-init-readiness-gate-design.md +233 -0
  27. package/families/dark/components/DriftingSpotlights.tsx +59 -0
  28. package/families/dark/components/FilmGrain.tsx +44 -0
  29. package/families/dark/components/ForestCard.tsx +43 -0
  30. package/families/dark/components/GridBackground.tsx +29 -0
  31. package/families/dark/components/RadialVignette.tsx +21 -0
  32. package/families/dark/components/Scanlines.tsx +35 -0
  33. package/families/dark/components/SegmentOpacity.ts +37 -0
  34. package/families/dark/components/index.ts +13 -0
  35. package/families/dark/index.ts +31 -0
  36. package/families/dark/palette.ts +98 -0
  37. package/families/dark/presets/claudedispatch.ts +46 -0
  38. package/families/dark/presets/codedrop.ts +37 -0
  39. package/families/dark/presets/gpt55.ts +54 -0
  40. package/families/dark/presets/notebooklm.ts +50 -0
  41. package/families/dark/presets/resourcescta.ts +35 -0
  42. package/families/dark/presets/skills.ts +40 -0
  43. package/families/dark/presets/stitch.ts +46 -0
  44. package/families/dark/presets/stitch2.ts +43 -0
  45. package/families/dark/typography.ts +16 -0
  46. package/families/forbidden/components/ForbiddenCausticBlobs.tsx +52 -0
  47. package/families/forbidden/components/NewsprintTexture.tsx +28 -0
  48. package/families/forbidden/components/TintedShadow.tsx +36 -0
  49. package/families/forbidden/components/index.ts +38 -0
  50. package/families/forbidden/index.ts +17 -0
  51. package/families/forbidden/palette.ts +88 -0
  52. package/families/forbidden/presets/heretic.ts +44 -0
  53. package/families/forbidden/typography.ts +18 -0
  54. package/families/glass/components/BreakdownCard.tsx +158 -0
  55. package/families/glass/components/CausticBlobs.tsx +49 -0
  56. package/families/glass/components/Counter.tsx +72 -0
  57. package/families/glass/components/EyebrowPill.tsx +59 -0
  58. package/families/glass/components/FilmStrip.tsx +202 -0
  59. package/families/glass/components/FloatingGlyphs.tsx +78 -0
  60. package/families/glass/components/GlassCard.tsx +58 -0
  61. package/families/glass/components/GlassCardBezel.tsx +45 -0
  62. package/families/glass/components/HairlineGrid.tsx +30 -0
  63. package/families/glass/components/IridescentRing.tsx +114 -0
  64. package/families/glass/components/IridescentText.tsx +98 -0
  65. package/families/glass/components/LightBeam.tsx +46 -0
  66. package/families/glass/components/ParticleBurst.tsx +62 -0
  67. package/families/glass/components/SonarRings.tsx +81 -0
  68. package/families/glass/components/StaggeredWords.tsx +74 -0
  69. package/families/glass/components/index.ts +20 -0
  70. package/families/glass/index.ts +31 -0
  71. package/families/glass/palette.ts +93 -0
  72. package/families/glass/presets/claudewatch.ts +64 -0
  73. package/families/glass/presets/claudewatchcta.ts +43 -0
  74. package/families/glass/presets/graphify.ts +45 -0
  75. package/families/glass/presets/gstack.ts +48 -0
  76. package/families/glass/presets/jcode.ts +50 -0
  77. package/families/glass/presets/lilagents.ts +52 -0
  78. package/families/glass/presets/paperclip.ts +43 -0
  79. package/families/glass/typography.ts +15 -0
  80. package/families/index.ts +49 -0
  81. package/families/paper/components/CardSpring.tsx +42 -0
  82. package/families/paper/components/CreamGrid.tsx +26 -0
  83. package/families/paper/components/EditorialSerifText.tsx +51 -0
  84. package/families/paper/components/GreenAccentCard.tsx +10 -0
  85. package/families/paper/components/PaperShadow.tsx +30 -0
  86. package/families/paper/components/ScaleBlurText.tsx +40 -0
  87. package/families/paper/components/index.ts +11 -0
  88. package/families/paper/index.ts +23 -0
  89. package/families/paper/palette.ts +102 -0
  90. package/families/paper/presets/designreel.ts +32 -0
  91. package/families/paper/presets/devini3d.ts +45 -0
  92. package/families/paper/presets/justdrop.ts +39 -0
  93. package/families/paper/presets/opus.ts +48 -0
  94. package/families/paper/typography.ts +17 -0
  95. package/families/warm/components/AccentGlow.tsx +60 -0
  96. package/families/warm/components/BentoCell.tsx +56 -0
  97. package/families/warm/components/BentoGrid.tsx +30 -0
  98. package/families/warm/components/FilmGrain.tsx +36 -0
  99. package/families/warm/components/ScaleBlurCounter.tsx +71 -0
  100. package/families/warm/components/WarmSurface.tsx +35 -0
  101. package/families/warm/components/index.ts +11 -0
  102. package/families/warm/index.ts +19 -0
  103. package/families/warm/palette.ts +81 -0
  104. package/families/warm/presets/huashu.ts +49 -0
  105. package/families/warm/presets/mempalace.ts +51 -0
  106. package/families/warm/typography.ts +17 -0
  107. package/package.json +85 -0
  108. package/reference/dark/claudedispatch.tsx +2441 -0
  109. package/reference/dark/notebooklm.tsx +2316 -0
  110. package/reference/dark/stitch.tsx +3040 -0
  111. package/reference/forbidden/heretic.tsx +2636 -0
  112. package/reference/glass/claudewatch.tsx +3827 -0
  113. package/reference/glass/graphify.tsx +2418 -0
  114. package/reference/glass/paperclip.tsx +2218 -0
  115. package/reference/paper/designreel.tsx +883 -0
  116. package/reference/paper/justdrop.tsx +1898 -0
  117. package/reference/paper/opus.tsx +1770 -0
  118. package/reference/warm/huashu.tsx +3413 -0
  119. package/reference/warm/mempalace.tsx +2909 -0
  120. package/skill/SKILL.md +229 -0
  121. package/skill/commands/reelstack-beats.md +20 -0
  122. package/skill/commands/reelstack-capture.md +24 -0
  123. package/skill/commands/reelstack-critique.md +15 -0
  124. package/skill/commands/reelstack-dark.md +40 -0
  125. package/skill/commands/reelstack-direction.md +17 -0
  126. package/skill/commands/reelstack-forbidden.md +25 -0
  127. package/skill/commands/reelstack-glass.md +39 -0
  128. package/skill/commands/reelstack-icons.md +22 -0
  129. package/skill/commands/reelstack-init.md +17 -0
  130. package/skill/commands/reelstack-lint.md +22 -0
  131. package/skill/commands/reelstack-paper.md +36 -0
  132. package/skill/commands/reelstack-render.md +20 -0
  133. package/skill/commands/reelstack-warm.md +36 -0
  134. package/templates/dark/template.tsx +115 -0
  135. package/templates/forbidden/template.tsx +111 -0
  136. package/templates/glass/template.tsx +201 -0
  137. package/templates/paper/template.tsx +133 -0
  138. package/templates/warm/template.tsx +210 -0
  139. package/utils/ai-purple-blocklist.ts +13 -0
  140. package/utils/banned-fonts.ts +11 -0
  141. package/utils/cubic-bezier.ts +36 -0
  142. package/utils/easing.ts +84 -0
  143. package/utils/grid.ts +13 -0
  144. package/utils/render-presets.json +56 -0
  145. package/utils/safe-zones.tsx +57 -0
@@ -0,0 +1,2418 @@
1
+ /**
2
+ * REFERENCE — GraphifyReel (Family: glass)
3
+ *
4
+ * Canonical example of the glass family's motion vocabulary, frame-locked
5
+ * BEAT structure, and scene choreography. Bundled with ReelStack v1.1.1+
6
+ * for STUDY and pattern adaptation.
7
+ *
8
+ * Asset imports stripped (look for REFERENCE-STRIP markers). Bring your own
9
+ * voiceover, brand SVGs, captures.
10
+ *
11
+ * License: study + adapt patterns OK. Verbatim re-publication as your own
12
+ * template NOT OK. See ReelStack LICENSE.
13
+ *
14
+ * Source: my-video/src/GraphifyReel.tsx
15
+ * Bundled at: 2026-05-08T18:50:39.497Z
16
+ */
17
+ import React from "react";
18
+ import {
19
+ AbsoluteFill,
20
+ Audio,
21
+ Easing,
22
+ Img,
23
+ OffthreadVideo,
24
+ Sequence,
25
+ interpolate,
26
+ spring,
27
+ staticFile,
28
+ useCurrentFrame,
29
+ useVideoConfig,
30
+ } from "remotion";
31
+ import { ds } from "./designSystem";
32
+
33
+ // ═══════════════════════════════════════════════════════════════
34
+ // TIMING — 65.2s @ 30fps = 1956 frames, audio-locked
35
+ // ═══════════════════════════════════════════════════════════════
36
+ export const GRAPHIFY_TOTAL = 1956;
37
+
38
+ export const BEAT = {
39
+ hook: 0,
40
+ problem: 180, // 6s
41
+ reveal: 540, // 18s
42
+ mechanism: 720, // 24s
43
+ clip: 1020, // 34s
44
+ numbers: 1320, // 44s
45
+ multimodal: 1500, // 50s
46
+ compat: 1680, // 56s
47
+ cta: 1830, // 61s
48
+ end: 1956, // 65.2s
49
+ } as const;
50
+
51
+ // GSAP-equivalent easing curves, mapped into Remotion's interpolate.
52
+ // power3Out, expoOut, backOut are the GSAP eases that give "premium spring" feel.
53
+ export const ease = {
54
+ power2Out: Easing.bezier(0.165, 0.84, 0.44, 1),
55
+ power3Out: Easing.bezier(0.215, 0.61, 0.355, 1),
56
+ power4Out: Easing.bezier(0.165, 0.84, 0.44, 1),
57
+ expoOut: Easing.bezier(0.19, 1, 0.22, 1),
58
+ expoIn: Easing.bezier(0.7, 0, 0.84, 0),
59
+ backOut: Easing.bezier(0.34, 1.56, 0.64, 1),
60
+ inOut: Easing.bezier(0.45, 0, 0.55, 1),
61
+ };
62
+
63
+ // ═══════════════════════════════════════════════════════════════
64
+ // LIGHT IRIDESCENT GLASS PALETTE
65
+ // ═══════════════════════════════════════════════════════════════
66
+ export const C = {
67
+ bg: "#EFEAF2",
68
+ bgWarm: "#E9E2EE",
69
+ bgCool: "#E4E9F2",
70
+ ink: "#0E0E12",
71
+ inkSoft: "#26242C",
72
+ inkMuted: "#5A5867",
73
+ inkDim: "#86848F",
74
+ hairline: "rgba(15,15,22,0.08)",
75
+ // Iridescent stops — desaturated agency-tier
76
+ iriCyan: "#7FE8D4",
77
+ iriViolet: "#8B7FE8",
78
+ iriRose: "#E89BC4",
79
+ iriGold: "#F2D88F",
80
+ // Glass
81
+ glassFill: "rgba(255,255,255,0.42)",
82
+ glassFillStrong: "rgba(255,255,255,0.62)",
83
+ glassBorder: "rgba(255,255,255,0.85)",
84
+ glassInner: "rgba(255,255,255,0.50)",
85
+ // Pill accents (frosted)
86
+ violetPill: "#9D8BF2",
87
+ tealPill: "#85DDC9",
88
+ } as const;
89
+
90
+ export const FONT = ds.font.sans;
91
+ export const MONO = ds.font.mono;
92
+
93
+ // ═══════════════════════════════════════════════════════════════
94
+ // GLASS PRIMITIVES
95
+ // ═══════════════════════════════════════════════════════════════
96
+ export const glassBase: React.CSSProperties = {
97
+ background: C.glassFill,
98
+ backdropFilter: "blur(32px) saturate(180%)",
99
+ WebkitBackdropFilter: "blur(32px) saturate(180%)",
100
+ border: `1.5px solid ${C.glassBorder}`,
101
+ boxShadow: [
102
+ "0 24px 48px -12px rgba(120,100,180,0.22)",
103
+ "0 8px 16px -4px rgba(120,100,180,0.12)",
104
+ "inset 0 1.5px 0 rgba(255,255,255,0.95)",
105
+ "inset 0 -1px 0 rgba(255,255,255,0.30)",
106
+ ].join(", "),
107
+ };
108
+
109
+ const SAFE_TOP = 290; // 15% of 1920
110
+ const SAFE_BOTTOM = 1500; // y where bottom 22% begins
111
+
112
+ // ═══════════════════════════════════════════════════════════════
113
+ // PERPETUAL CAUSTIC BLOBS — drift slowly, simulate iridescent reflections
114
+ // ═══════════════════════════════════════════════════════════════
115
+ export const CausticBlobs: React.FC = () => {
116
+ const frame = useCurrentFrame();
117
+ const t = frame / 30;
118
+ const blob = (
119
+ color: string,
120
+ size: number,
121
+ cx: number,
122
+ cy: number,
123
+ speedX: number,
124
+ speedY: number,
125
+ phase: number,
126
+ opacity = 0.55,
127
+ ): React.CSSProperties => ({
128
+ position: "absolute",
129
+ width: size,
130
+ height: size,
131
+ borderRadius: "50%",
132
+ background: `radial-gradient(circle at 50% 50%, ${color} 0%, ${color}99 30%, transparent 70%)`,
133
+ filter: "blur(120px)",
134
+ left: cx + Math.sin(t * speedX + phase) * 220,
135
+ top: cy + Math.cos(t * speedY + phase) * 160,
136
+ opacity,
137
+ pointerEvents: "none",
138
+ willChange: "transform",
139
+ });
140
+
141
+ return (
142
+ <AbsoluteFill style={{ overflow: "hidden" }}>
143
+ <div style={blob(C.iriCyan, 720, 80, 220, 0.18, 0.13, 0.0, 0.7)} />
144
+ <div style={blob(C.iriViolet, 820, 600, 540, 0.14, 0.16, 1.2, 0.65)} />
145
+ <div style={blob(C.iriRose, 700, 200, 1100, 0.11, 0.19, 2.4, 0.55)} />
146
+ <div style={blob(C.iriGold, 540, 720, 1500, 0.16, 0.12, 3.6, 0.4)} />
147
+ <div style={blob(C.iriCyan, 600, 100, 1700, 0.12, 0.18, 4.2, 0.4)} />
148
+ </AbsoluteFill>
149
+ );
150
+ };
151
+
152
+ // ═══════════════════════════════════════════════════════════════
153
+ // HAIRLINE GRID — subtle 32-cell grid for depth
154
+ // ═══════════════════════════════════════════════════════════════
155
+ export const HairlineGrid: React.FC<{ opacity?: number }> = ({ opacity = 0.06 }) => {
156
+ return (
157
+ <AbsoluteFill
158
+ style={{
159
+ backgroundImage: `linear-gradient(${C.ink} 1px, transparent 1px), linear-gradient(90deg, ${C.ink} 1px, transparent 1px)`,
160
+ backgroundSize: "60px 60px",
161
+ opacity,
162
+ pointerEvents: "none",
163
+ }}
164
+ />
165
+ );
166
+ };
167
+
168
+ // ═══════════════════════════════════════════════════════════════
169
+ // IRIDESCENT RING — conic-gradient ring that rotates slowly
170
+ // ═══════════════════════════════════════════════════════════════
171
+ export const IridescentRing: React.FC<{
172
+ size: number;
173
+ thickness?: number;
174
+ speed?: number;
175
+ borderRadius?: number;
176
+ }> = ({ size, thickness = 4, speed = 0.5, borderRadius = 9999 }) => {
177
+ const frame = useCurrentFrame();
178
+ const angle = (frame * speed) % 360;
179
+ return (
180
+ <div
181
+ style={{
182
+ position: "absolute",
183
+ inset: -thickness,
184
+ width: size + thickness * 2,
185
+ height: size + thickness * 2,
186
+ borderRadius: borderRadius + thickness,
187
+ background: `conic-gradient(from ${angle}deg, ${C.iriCyan}, ${C.iriViolet}, ${C.iriRose}, ${C.iriGold}, ${C.iriCyan})`,
188
+ padding: thickness,
189
+ pointerEvents: "none",
190
+ opacity: 0.85,
191
+ }}
192
+ >
193
+ <div
194
+ style={{
195
+ width: "100%",
196
+ height: "100%",
197
+ background: C.bg,
198
+ borderRadius,
199
+ }}
200
+ />
201
+ </div>
202
+ );
203
+ };
204
+
205
+ // ═══════════════════════════════════════════════════════════════
206
+ // GLASS CARD — double-bezel architecture
207
+ // ═══════════════════════════════════════════════════════════════
208
+ export const GlassCard: React.FC<{
209
+ style?: React.CSSProperties;
210
+ children?: React.ReactNode;
211
+ radius?: number;
212
+ }> = ({ style, children, radius = 32 }) => (
213
+ <div
214
+ style={{
215
+ ...glassBase,
216
+ borderRadius: radius,
217
+ ...style,
218
+ }}
219
+ >
220
+ {children}
221
+ </div>
222
+ );
223
+
224
+ // ═══════════════════════════════════════════════════════════════
225
+ // EYEBROW PILL — small glass capsule with tracking-wide caps
226
+ // ═══════════════════════════════════════════════════════════════
227
+ export const EyebrowPill: React.FC<{ children: React.ReactNode }> = ({
228
+ children,
229
+ }) => (
230
+ <div
231
+ style={{
232
+ ...glassBase,
233
+ borderRadius: 9999,
234
+ padding: "10px 22px",
235
+ display: "inline-flex",
236
+ alignItems: "center",
237
+ gap: 10,
238
+ fontFamily: MONO,
239
+ fontSize: 18,
240
+ fontWeight: 500,
241
+ color: C.inkSoft,
242
+ letterSpacing: "0.18em",
243
+ textTransform: "uppercase",
244
+ }}
245
+ >
246
+ <div
247
+ style={{
248
+ width: 7,
249
+ height: 7,
250
+ borderRadius: "50%",
251
+ background: C.iriViolet,
252
+ boxShadow: `0 0 14px ${C.iriViolet}`,
253
+ }}
254
+ />
255
+ {children}
256
+ </div>
257
+ );
258
+
259
+ // ═══════════════════════════════════════════════════════════════
260
+ // STAGGERED WORD REVEAL — fade-up with blur, GSAP-style stagger
261
+ // ═══════════════════════════════════════════════════════════════
262
+ export const StaggeredWords: React.FC<{
263
+ text: string;
264
+ startFrame: number;
265
+ perWordDelay?: number;
266
+ duration?: number;
267
+ fontSize: number;
268
+ fontWeight?: number;
269
+ color?: string;
270
+ letterSpacing?: string;
271
+ lineHeight?: number;
272
+ align?: "left" | "center" | "right";
273
+ fontFamily?: string;
274
+ highlight?: string;
275
+ highlightColor?: string;
276
+ }> = ({
277
+ text,
278
+ startFrame,
279
+ perWordDelay = 4,
280
+ duration = 24,
281
+ fontSize,
282
+ fontWeight = 700,
283
+ color = C.ink,
284
+ letterSpacing = "-0.035em",
285
+ lineHeight = 1.02,
286
+ align = "left",
287
+ fontFamily = FONT,
288
+ highlight,
289
+ highlightColor = C.iriViolet,
290
+ }) => {
291
+ const frame = useCurrentFrame();
292
+ const words = text.split(" ");
293
+ return (
294
+ <div
295
+ style={{
296
+ display: "flex",
297
+ flexWrap: "wrap",
298
+ gap: "0.25em",
299
+ justifyContent:
300
+ align === "center"
301
+ ? "center"
302
+ : align === "right"
303
+ ? "flex-end"
304
+ : "flex-start",
305
+ textAlign: align,
306
+ fontFamily,
307
+ fontSize,
308
+ fontWeight,
309
+ color,
310
+ letterSpacing,
311
+ lineHeight,
312
+ }}
313
+ >
314
+ {words.map((w, i) => {
315
+ const wordStart = startFrame + i * perWordDelay;
316
+ const local = frame - wordStart;
317
+ const opacity = interpolate(local, [0, duration], [0, 1], {
318
+ extrapolateLeft: "clamp",
319
+ extrapolateRight: "clamp",
320
+ easing: ease.expoOut,
321
+ });
322
+ const y = interpolate(local, [0, duration], [40, 0], {
323
+ extrapolateLeft: "clamp",
324
+ extrapolateRight: "clamp",
325
+ easing: ease.power3Out,
326
+ });
327
+ const blur = interpolate(local, [0, duration], [12, 0], {
328
+ extrapolateLeft: "clamp",
329
+ extrapolateRight: "clamp",
330
+ });
331
+ const isHighlight = highlight && w.toLowerCase().includes(highlight.toLowerCase());
332
+ return (
333
+ <span
334
+ key={i}
335
+ style={{
336
+ display: "inline-block",
337
+ opacity,
338
+ transform: `translateY(${y}px)`,
339
+ filter: `blur(${blur}px)`,
340
+ color: isHighlight ? highlightColor : color,
341
+ willChange: "transform, opacity, filter",
342
+ }}
343
+ >
344
+ {w}
345
+ </span>
346
+ );
347
+ })}
348
+ </div>
349
+ );
350
+ };
351
+
352
+ // ═══════════════════════════════════════════════════════════════
353
+ // SCRAMBLE TEXT — characters resolve from random to actual
354
+ // ═══════════════════════════════════════════════════════════════
355
+ export const ScrambleText: React.FC<{
356
+ text: string;
357
+ startFrame: number;
358
+ duration: number;
359
+ fontSize: number;
360
+ fontWeight?: number;
361
+ color?: string;
362
+ fontFamily?: string;
363
+ letterSpacing?: string;
364
+ }> = ({
365
+ text,
366
+ startFrame,
367
+ duration,
368
+ fontSize,
369
+ fontWeight = 800,
370
+ color = C.ink,
371
+ fontFamily = FONT,
372
+ letterSpacing = "-0.06em",
373
+ }) => {
374
+ const frame = useCurrentFrame();
375
+ const local = Math.max(0, frame - startFrame);
376
+ const progress = Math.min(1, local / duration);
377
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#%&!*";
378
+ const resolved = Math.floor(progress * text.length);
379
+ const out = text
380
+ .split("")
381
+ .map((ch, i) => {
382
+ if (i < resolved) return ch;
383
+ if (ch === " ") return " ";
384
+ const seed = (frame + i * 7) % chars.length;
385
+ return chars[seed];
386
+ })
387
+ .join("");
388
+ return (
389
+ <div
390
+ style={{
391
+ fontFamily,
392
+ fontSize,
393
+ fontWeight,
394
+ color,
395
+ letterSpacing,
396
+ lineHeight: 1,
397
+ whiteSpace: "pre",
398
+ }}
399
+ >
400
+ {out}
401
+ </div>
402
+ );
403
+ };
404
+
405
+ // ═══════════════════════════════════════════════════════════════
406
+ // COUNTER — easing number ticker
407
+ // ═══════════════════════════════════════════════════════════════
408
+ export const Counter: React.FC<{
409
+ from: number;
410
+ to: number;
411
+ startFrame: number;
412
+ duration: number;
413
+ format?: (n: number) => string;
414
+ style?: React.CSSProperties;
415
+ easing?: (n: number) => number;
416
+ }> = ({ from, to, startFrame, duration, format, style, easing = ease.expoOut }) => {
417
+ const frame = useCurrentFrame();
418
+ const value = interpolate(frame, [startFrame, startFrame + duration], [from, to], {
419
+ extrapolateLeft: "clamp",
420
+ extrapolateRight: "clamp",
421
+ easing,
422
+ });
423
+ return <span style={style}>{format ? format(value) : Math.round(value).toString()}</span>;
424
+ };
425
+
426
+ // ═══════════════════════════════════════════════════════════════
427
+ // SCENE 1 — HOOK (0-180, 6s) — motion-art-heavy opener
428
+ // "Before you open Claude Code again, you need to see this."
429
+ // ═══════════════════════════════════════════════════════════════
430
+
431
+ // Sonar rings — concentric circles pulsing outward from center
432
+ export const SonarRings: React.FC = () => {
433
+ const frame = useCurrentFrame();
434
+ const rings = [0, 18, 36, 54, 72]; // staggered birth frames
435
+ return (
436
+ <AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
437
+ {rings.map((birth, i) => {
438
+ const local = frame - birth;
439
+ const cycle = local % 90; // ring lives for 90 frames
440
+ if (local < 0) return null;
441
+ const scale = interpolate(cycle, [0, 90], [0.05, 1.4], {
442
+ extrapolateLeft: "clamp",
443
+ extrapolateRight: "clamp",
444
+ easing: ease.expoOut,
445
+ });
446
+ const op = interpolate(cycle, [0, 30, 90], [0, 0.55, 0], {
447
+ extrapolateLeft: "clamp",
448
+ extrapolateRight: "clamp",
449
+ });
450
+ return (
451
+ <div
452
+ key={i}
453
+ style={{
454
+ position: "absolute",
455
+ width: 1400,
456
+ height: 1400,
457
+ borderRadius: "50%",
458
+ border: `2px solid ${i % 2 === 0 ? C.iriViolet : C.iriCyan}`,
459
+ transform: `scale(${scale})`,
460
+ opacity: op,
461
+ willChange: "transform, opacity",
462
+ }}
463
+ />
464
+ );
465
+ })}
466
+ </AbsoluteFill>
467
+ );
468
+ };
469
+
470
+ // Particle burst — small glass dots scatter outward from center
471
+ export const ParticleBurst: React.FC = () => {
472
+ const frame = useCurrentFrame();
473
+ const particles = Array.from({ length: 48 }, (_, i) => {
474
+ const angle = (i / 48) * Math.PI * 2 + (i * 0.31);
475
+ const distance = 200 + (i % 7) * 80;
476
+ const speed = 0.7 + (i % 5) * 0.18;
477
+ const size = 6 + (i % 4) * 4;
478
+ const colorIdx = i % 4;
479
+ const color = [C.iriCyan, C.iriViolet, C.iriRose, C.iriGold][colorIdx];
480
+ const delay = (i * 0.7) % 24;
481
+ return { angle, distance, speed, size, color, delay };
482
+ });
483
+
484
+ return (
485
+ <AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
486
+ {particles.map((p, i) => {
487
+ const local = Math.max(0, frame - p.delay);
488
+ // Burst out then drift slowly
489
+ const burstProgress = interpolate(local, [0, 50], [0, 1], {
490
+ extrapolateLeft: "clamp",
491
+ extrapolateRight: "clamp",
492
+ easing: ease.expoOut,
493
+ });
494
+ const drift = Math.sin((frame + i * 12) * 0.04) * 12;
495
+ const x = Math.cos(p.angle) * p.distance * burstProgress + drift;
496
+ const y = Math.sin(p.angle) * p.distance * burstProgress * 1.4 + drift * 0.6;
497
+ const op = interpolate(local, [0, 20, 100, 180], [0, 0.85, 0.5, 0.2], {
498
+ extrapolateLeft: "clamp",
499
+ extrapolateRight: "clamp",
500
+ });
501
+ return (
502
+ <div
503
+ key={i}
504
+ style={{
505
+ position: "absolute",
506
+ width: p.size,
507
+ height: p.size,
508
+ borderRadius: "50%",
509
+ background: p.color,
510
+ boxShadow: `0 0 ${p.size * 2}px ${p.color}`,
511
+ transform: `translate(${x}px, ${y}px)`,
512
+ opacity: op,
513
+ willChange: "transform, opacity",
514
+ }}
515
+ />
516
+ );
517
+ })}
518
+ </AbsoluteFill>
519
+ );
520
+ };
521
+
522
+ // Light beam sweep — diagonal iridescent ray that sweeps across
523
+ export const LightBeam: React.FC<{ delay: number; angle: number }> = ({ delay, angle }) => {
524
+ const frame = useCurrentFrame();
525
+ const local = frame - delay;
526
+ const progress = interpolate(local, [0, 60], [-0.3, 1.3], {
527
+ extrapolateLeft: "clamp",
528
+ extrapolateRight: "clamp",
529
+ easing: ease.expoOut,
530
+ });
531
+ const op = interpolate(local, [0, 12, 50, 60], [0, 0.7, 0.7, 0], {
532
+ extrapolateLeft: "clamp",
533
+ extrapolateRight: "clamp",
534
+ });
535
+ return (
536
+ <div
537
+ style={{
538
+ position: "absolute",
539
+ top: "50%",
540
+ left: "50%",
541
+ width: 2200,
542
+ height: 100,
543
+ background: `linear-gradient(90deg, transparent 0%, ${C.iriCyan}88 30%, ${C.iriViolet}cc 50%, ${C.iriRose}88 70%, transparent 100%)`,
544
+ filter: "blur(20px)",
545
+ transform: `translate(-50%, -50%) rotate(${angle}deg) translateX(${(progress - 0.5) * 1500}px)`,
546
+ opacity: op,
547
+ willChange: "transform, opacity",
548
+ }}
549
+ />
550
+ );
551
+ };
552
+
553
+ // Kinetic glyph — tiny floating UI shards in background
554
+ export const FloatingGlyphs: React.FC = () => {
555
+ const frame = useCurrentFrame();
556
+ const glyphs = [
557
+ { x: 120, y: 380, size: 80, delay: 4, rot: -8 },
558
+ { x: 880, y: 340, size: 60, delay: 12, rot: 12 },
559
+ { x: 80, y: 1240, size: 70, delay: 22, rot: 6 },
560
+ { x: 920, y: 1280, size: 90, delay: 32, rot: -10 },
561
+ { x: 200, y: 800, size: 50, delay: 42, rot: 14 },
562
+ { x: 820, y: 760, size: 55, delay: 52, rot: -4 },
563
+ ];
564
+ return (
565
+ <>
566
+ {glyphs.map((g, i) => {
567
+ const local = frame - g.delay;
568
+ const op = interpolate(local, [0, 30], [0, 1], {
569
+ extrapolateLeft: "clamp",
570
+ extrapolateRight: "clamp",
571
+ easing: ease.expoOut,
572
+ });
573
+ const float = Math.sin((frame + i * 22) * 0.05) * 8;
574
+ const scale = spring({
575
+ frame: local,
576
+ fps: 30,
577
+ config: { damping: 12, stiffness: 110 },
578
+ from: 0.6,
579
+ to: 1,
580
+ });
581
+ return (
582
+ <div
583
+ key={i}
584
+ style={{
585
+ position: "absolute",
586
+ left: g.x,
587
+ top: g.y + float,
588
+ width: g.size,
589
+ height: g.size,
590
+ borderRadius: g.size * 0.28,
591
+ background: C.glassFill,
592
+ backdropFilter: "blur(20px) saturate(160%)",
593
+ WebkitBackdropFilter: "blur(20px) saturate(160%)",
594
+ border: `1px solid ${C.glassBorder}`,
595
+ boxShadow: "inset 0 1px 0 rgba(255,255,255,0.85), 0 8px 16px -4px rgba(120,100,180,0.18)",
596
+ transform: `rotate(${g.rot}deg) scale(${scale})`,
597
+ opacity: op * 0.85,
598
+ willChange: "transform, opacity",
599
+ }}
600
+ />
601
+ );
602
+ })}
603
+ </>
604
+ );
605
+ };
606
+
607
+ const HookScene: React.FC = () => {
608
+ const frame = useCurrentFrame();
609
+
610
+ // Heavy hero-text scale-pop
611
+ const heroScale = spring({
612
+ frame: frame - 6,
613
+ fps: 30,
614
+ config: { damping: 14, stiffness: 130 },
615
+ from: 0.7,
616
+ to: 1,
617
+ });
618
+ const heroBlur = interpolate(frame, [6, 32], [16, 0], {
619
+ extrapolateLeft: "clamp",
620
+ extrapolateRight: "clamp",
621
+ easing: ease.expoOut,
622
+ });
623
+
624
+ return (
625
+ <AbsoluteFill>
626
+ {/* Motion-art layer 1: sonar rings */}
627
+ <SonarRings />
628
+
629
+ {/* Motion-art layer 2: light beam sweeps */}
630
+ <LightBeam delay={0} angle={-18} />
631
+ <LightBeam delay={26} angle={22} />
632
+ <LightBeam delay={60} angle={-12} />
633
+
634
+ {/* Motion-art layer 3: floating glass glyphs */}
635
+ <FloatingGlyphs />
636
+
637
+ {/* Motion-art layer 4: particle burst */}
638
+ <ParticleBurst />
639
+
640
+ {/* Eyebrow */}
641
+ <div
642
+ style={{
643
+ position: "absolute",
644
+ top: SAFE_TOP + 24,
645
+ left: 0,
646
+ right: 0,
647
+ display: "flex",
648
+ justifyContent: "center",
649
+ opacity: interpolate(frame, [4, 24], [0, 1], { extrapolateRight: "clamp" }),
650
+ transform: `translateY(${interpolate(frame, [4, 24], [16, 0], { extrapolateRight: "clamp", easing: ease.power3Out })}px)`,
651
+ }}
652
+ >
653
+ <EyebrowPill>01 — heads up</EyebrowPill>
654
+ </div>
655
+
656
+ {/* Hero copy — center, with scale-pop + blur clear */}
657
+ <AbsoluteFill
658
+ style={{
659
+ alignItems: "center",
660
+ justifyContent: "center",
661
+ padding: "0 64px",
662
+ transform: `scale(${heroScale})`,
663
+ filter: `blur(${heroBlur}px)`,
664
+ willChange: "transform, filter",
665
+ }}
666
+ >
667
+ <div style={{ width: "100%" }}>
668
+ <StaggeredWords
669
+ text="Before you open"
670
+ startFrame={6}
671
+ perWordDelay={3}
672
+ fontSize={92}
673
+ fontWeight={500}
674
+ color={C.inkMuted}
675
+ align="center"
676
+ letterSpacing="-0.025em"
677
+ />
678
+ <div style={{ height: 4 }} />
679
+ <StaggeredWords
680
+ text="Claude Code"
681
+ startFrame={20}
682
+ perWordDelay={3}
683
+ fontSize={148}
684
+ fontWeight={800}
685
+ color={C.ink}
686
+ align="center"
687
+ letterSpacing="-0.05em"
688
+ lineHeight={1}
689
+ />
690
+ <div style={{ height: 4 }} />
691
+ <StaggeredWords
692
+ text="again,"
693
+ startFrame={36}
694
+ perWordDelay={3}
695
+ fontSize={148}
696
+ fontWeight={800}
697
+ color={C.ink}
698
+ align="center"
699
+ letterSpacing="-0.05em"
700
+ lineHeight={1}
701
+ />
702
+ </div>
703
+
704
+ {/* Iridescent divider accent */}
705
+ <div
706
+ style={{
707
+ width: 280,
708
+ height: 5,
709
+ borderRadius: 999,
710
+ margin: "48px 0",
711
+ background: `linear-gradient(90deg, ${C.iriCyan}, ${C.iriViolet}, ${C.iriRose})`,
712
+ opacity: interpolate(frame, [50, 80], [0, 1], { extrapolateRight: "clamp" }),
713
+ transform: `scaleX(${interpolate(frame, [50, 90], [0, 1], { extrapolateRight: "clamp", easing: ease.expoOut })})`,
714
+ transformOrigin: "center",
715
+ boxShadow: `0 0 24px ${C.iriViolet}66`,
716
+ }}
717
+ />
718
+
719
+ {/* Closing line */}
720
+ <div style={{ width: "100%" }}>
721
+ <StaggeredWords
722
+ text="you need to see this."
723
+ startFrame={70}
724
+ fontSize={76}
725
+ fontWeight={700}
726
+ color={C.ink}
727
+ align="center"
728
+ letterSpacing="-0.03em"
729
+ highlight="see"
730
+ highlightColor={C.iriViolet}
731
+ />
732
+ </div>
733
+ </AbsoluteFill>
734
+ </AbsoluteFill>
735
+ );
736
+ };
737
+
738
+ // ═══════════════════════════════════════════════════════════════
739
+ // SCENE 2 — PROBLEM (180-540, 12s)
740
+ // "Every new session, Claude re-reads your entire codebase.
741
+ // Thousands of tokens, gone."
742
+ // ═══════════════════════════════════════════════════════════════
743
+ const ProblemScene: React.FC = () => {
744
+ const frame = useCurrentFrame();
745
+ const local = frame - BEAT.problem;
746
+
747
+ const tokensFrom = 200000;
748
+ const tokensTo = 0;
749
+
750
+ // Glass terminal panel reveal
751
+ const panelOpacity = interpolate(local, [0, 24], [0, 1], {
752
+ extrapolateRight: "clamp",
753
+ easing: ease.expoOut,
754
+ });
755
+ const panelY = interpolate(local, [0, 30], [40, 0], {
756
+ extrapolateRight: "clamp",
757
+ easing: ease.power3Out,
758
+ });
759
+
760
+ const codeLines = [
761
+ "src/app/(dashboard)/layout.tsx",
762
+ "src/components/sidebar/index.tsx",
763
+ "src/lib/auth/session.ts",
764
+ "src/lib/db/schema.ts",
765
+ "src/components/ui/button.tsx",
766
+ "src/app/api/users/route.ts",
767
+ "src/hooks/use-toast.ts",
768
+ "src/components/forms/login.tsx",
769
+ "src/app/page.tsx",
770
+ "src/lib/utils.ts",
771
+ ];
772
+
773
+ return (
774
+ <AbsoluteFill>
775
+ {/* Eyebrow */}
776
+ <div
777
+ style={{
778
+ position: "absolute",
779
+ top: SAFE_TOP + 24,
780
+ left: 0,
781
+ right: 0,
782
+ display: "flex",
783
+ justifyContent: "center",
784
+ opacity: interpolate(local, [0, 20], [0, 1], { extrapolateRight: "clamp" }),
785
+ }}
786
+ >
787
+ <EyebrowPill>02 — the silent drain</EyebrowPill>
788
+ </div>
789
+
790
+ {/* Headline */}
791
+ <div style={{ position: "absolute", left: 80, right: 80, top: SAFE_TOP + 110 }}>
792
+ <StaggeredWords
793
+ text="Every new session,"
794
+ startFrame={BEAT.problem + 10}
795
+ fontSize={68}
796
+ fontWeight={500}
797
+ color={C.inkMuted}
798
+ align="left"
799
+ />
800
+ <div style={{ height: 8 }} />
801
+ <StaggeredWords
802
+ text="Claude re-reads your"
803
+ startFrame={BEAT.problem + 30}
804
+ fontSize={86}
805
+ fontWeight={700}
806
+ color={C.ink}
807
+ align="left"
808
+ />
809
+ <StaggeredWords
810
+ text="entire codebase."
811
+ startFrame={BEAT.problem + 55}
812
+ fontSize={86}
813
+ fontWeight={700}
814
+ color={C.ink}
815
+ align="left"
816
+ highlight="entire"
817
+ highlightColor={C.iriViolet}
818
+ />
819
+ </div>
820
+
821
+ {/* Glass terminal panel with cascading code */}
822
+ <div
823
+ style={{
824
+ position: "absolute",
825
+ left: 80,
826
+ top: 850,
827
+ width: 920,
828
+ height: 480,
829
+ opacity: panelOpacity,
830
+ transform: `translateY(${panelY}px)`,
831
+ willChange: "transform",
832
+ }}
833
+ >
834
+ <GlassCard radius={28} style={{ width: "100%", height: "100%", padding: 32, overflow: "hidden", position: "relative" }}>
835
+ {/* Window controls */}
836
+ <div style={{ display: "flex", gap: 8, marginBottom: 22 }}>
837
+ <div style={{ width: 12, height: 12, borderRadius: 6, background: "#FF5F57" }} />
838
+ <div style={{ width: 12, height: 12, borderRadius: 6, background: "#FEBC2E" }} />
839
+ <div style={{ width: 12, height: 12, borderRadius: 6, background: "#28C840" }} />
840
+ <div
841
+ style={{
842
+ marginLeft: "auto",
843
+ fontFamily: MONO,
844
+ fontSize: 14,
845
+ color: C.inkDim,
846
+ letterSpacing: "0.05em",
847
+ }}
848
+ >
849
+ claude-code · session/new
850
+ </div>
851
+ </div>
852
+ {codeLines.map((line, i) => {
853
+ const startF = BEAT.problem + 50 + i * 8;
854
+ const lf = frame - startF;
855
+ const op = interpolate(lf, [0, 14], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
856
+ const x = interpolate(lf, [0, 14], [-24, 0], {
857
+ extrapolateLeft: "clamp",
858
+ extrapolateRight: "clamp",
859
+ easing: ease.power3Out,
860
+ });
861
+ // Fade out as tokens drain
862
+ const fadeOut = interpolate(
863
+ local,
864
+ [180, 280],
865
+ [1, 0.18],
866
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" },
867
+ );
868
+ return (
869
+ <div
870
+ key={i}
871
+ style={{
872
+ fontFamily: MONO,
873
+ fontSize: 22,
874
+ color: C.inkSoft,
875
+ marginBottom: 8,
876
+ opacity: op * fadeOut,
877
+ transform: `translateX(${x}px)`,
878
+ display: "flex",
879
+ gap: 18,
880
+ }}
881
+ >
882
+ <span style={{ color: C.inkDim }}>read</span>
883
+ <span>{line}</span>
884
+ <span
885
+ style={{
886
+ marginLeft: "auto",
887
+ color: C.iriViolet,
888
+ fontVariantNumeric: "tabular-nums",
889
+ }}
890
+ >
891
+ −{(7000 + i * 400).toLocaleString()}t
892
+ </span>
893
+ </div>
894
+ );
895
+ })}
896
+
897
+ {/* Token counter overlay (bottom) */}
898
+ <div
899
+ style={{
900
+ position: "absolute",
901
+ left: 32,
902
+ right: 32,
903
+ bottom: 28,
904
+ display: "flex",
905
+ alignItems: "baseline",
906
+ justifyContent: "space-between",
907
+ borderTop: `1px solid ${C.hairline}`,
908
+ paddingTop: 18,
909
+ }}
910
+ >
911
+ <div
912
+ style={{
913
+ fontFamily: MONO,
914
+ fontSize: 14,
915
+ color: C.inkDim,
916
+ textTransform: "uppercase",
917
+ letterSpacing: "0.18em",
918
+ }}
919
+ >
920
+ context tokens
921
+ </div>
922
+ <div
923
+ style={{
924
+ fontFamily: MONO,
925
+ fontSize: 38,
926
+ fontWeight: 600,
927
+ color: C.ink,
928
+ fontVariantNumeric: "tabular-nums",
929
+ }}
930
+ >
931
+ <Counter
932
+ from={tokensFrom}
933
+ to={tokensTo}
934
+ startFrame={BEAT.problem + 110}
935
+ duration={140}
936
+ format={(n) => Math.round(n).toLocaleString()}
937
+ />
938
+ </div>
939
+ </div>
940
+ </GlassCard>
941
+ </div>
942
+
943
+ {/* "gone." stamp at end of scene */}
944
+ <div
945
+ style={{
946
+ position: "absolute",
947
+ left: 0,
948
+ right: 0,
949
+ top: 1380,
950
+ display: "flex",
951
+ justifyContent: "center",
952
+ opacity: interpolate(local, [260, 300], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" }),
953
+ transform: `scale(${interpolate(local, [260, 320], [0.8, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: ease.backOut })})`,
954
+ }}
955
+ >
956
+ <div
957
+ style={{
958
+ fontFamily: FONT,
959
+ fontSize: 140,
960
+ fontWeight: 800,
961
+ color: C.ink,
962
+ letterSpacing: "-0.06em",
963
+ lineHeight: 1,
964
+ }}
965
+ >
966
+ gone.
967
+ </div>
968
+ </div>
969
+ </AbsoluteFill>
970
+ );
971
+ };
972
+
973
+ // ═══════════════════════════════════════════════════════════════
974
+ // SCENE 3 — REVEAL (540-720, 6s)
975
+ // "It's called Graphify."
976
+ // ═══════════════════════════════════════════════════════════════
977
+ const RevealScene: React.FC = () => {
978
+ const frame = useCurrentFrame();
979
+ const local = frame - BEAT.reveal;
980
+
981
+ const introOpacity = interpolate(local, [0, 24], [0, 1], { extrapolateRight: "clamp" });
982
+ const titleOpacity = interpolate(local, [30, 60], [0, 1], { extrapolateRight: "clamp" });
983
+ const titleY = interpolate(local, [30, 70], [50, 0], {
984
+ extrapolateRight: "clamp",
985
+ easing: ease.expoOut,
986
+ });
987
+ const titleScale = spring({
988
+ frame: local - 30,
989
+ fps: 30,
990
+ config: { damping: 14, stiffness: 110 },
991
+ from: 0.92,
992
+ to: 1,
993
+ });
994
+
995
+ const ringRevealStart = 60;
996
+ const ringSize = interpolate(local, [ringRevealStart, ringRevealStart + 40], [0, 920], {
997
+ extrapolateLeft: "clamp",
998
+ extrapolateRight: "clamp",
999
+ easing: ease.expoOut,
1000
+ });
1001
+
1002
+ return (
1003
+ <AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
1004
+ <div
1005
+ style={{
1006
+ fontFamily: FONT,
1007
+ fontSize: 56,
1008
+ fontWeight: 500,
1009
+ color: C.inkMuted,
1010
+ opacity: introOpacity,
1011
+ transform: `translateY(${interpolate(local, [0, 24], [16, 0], { extrapolateRight: "clamp", easing: ease.power3Out })}px)`,
1012
+ marginBottom: 28,
1013
+ letterSpacing: "-0.02em",
1014
+ }}
1015
+ >
1016
+ It's called
1017
+ </div>
1018
+
1019
+ {/* Title group with iridescent ring behind */}
1020
+ <div
1021
+ style={{
1022
+ position: "relative",
1023
+ opacity: titleOpacity,
1024
+ transform: `translateY(${titleY}px) scale(${titleScale})`,
1025
+ willChange: "transform, opacity",
1026
+ }}
1027
+ >
1028
+ {/* Conic iridescent halo */}
1029
+ <div
1030
+ style={{
1031
+ position: "absolute",
1032
+ left: "50%",
1033
+ top: "50%",
1034
+ width: ringSize,
1035
+ height: ringSize,
1036
+ transform: "translate(-50%, -50%)",
1037
+ borderRadius: "50%",
1038
+ background: `conic-gradient(from ${frame * 0.6}deg, ${C.iriCyan}, ${C.iriViolet}, ${C.iriRose}, ${C.iriGold}, ${C.iriCyan})`,
1039
+ filter: "blur(80px)",
1040
+ opacity: 0.55,
1041
+ }}
1042
+ />
1043
+ <div
1044
+ style={{
1045
+ position: "relative",
1046
+ fontFamily: FONT,
1047
+ fontSize: 220,
1048
+ fontWeight: 800,
1049
+ color: C.ink,
1050
+ letterSpacing: "-0.07em",
1051
+ lineHeight: 0.9,
1052
+ }}
1053
+ >
1054
+ Graphify.
1055
+ </div>
1056
+ </div>
1057
+
1058
+ {/* Tagline */}
1059
+ <div
1060
+ style={{
1061
+ marginTop: 28,
1062
+ opacity: interpolate(local, [80, 120], [0, 1], { extrapolateRight: "clamp" }),
1063
+ transform: `translateY(${interpolate(local, [80, 120], [12, 0], { extrapolateRight: "clamp", easing: ease.power3Out })}px)`,
1064
+ fontFamily: MONO,
1065
+ fontSize: 22,
1066
+ color: C.inkMuted,
1067
+ letterSpacing: "0.18em",
1068
+ textTransform: "uppercase",
1069
+ }}
1070
+ >
1071
+ a knowledge graph for your code
1072
+ </div>
1073
+ </AbsoluteFill>
1074
+ );
1075
+ };
1076
+
1077
+ // ═══════════════════════════════════════════════════════════════
1078
+ // SCENE 4 — MECHANISM (720-1020, 10s)
1079
+ // "GitHub — 35,000 stars and counting." +
1080
+ // "Builds a full knowledge graph"
1081
+ // ═══════════════════════════════════════════════════════════════
1082
+ export const KnowledgeGraph: React.FC<{ startFrame: number }> = ({ startFrame }) => {
1083
+ const frame = useCurrentFrame();
1084
+ // 9 nodes positioned in a constellation
1085
+ const nodes = [
1086
+ { x: 220, y: 200, r: 22, label: "auth", color: C.iriCyan },
1087
+ { x: 90, y: 360, r: 16, label: "db", color: C.iriViolet },
1088
+ { x: 380, y: 320, r: 30, label: "api", color: C.iriRose },
1089
+ { x: 540, y: 180, r: 18, label: "ui", color: C.iriGold },
1090
+ { x: 680, y: 380, r: 24, label: "hooks", color: C.iriViolet },
1091
+ { x: 280, y: 540, r: 20, label: "lib", color: C.iriCyan },
1092
+ { x: 480, y: 580, r: 16, label: "utils", color: C.iriRose },
1093
+ { x: 620, y: 540, r: 22, label: "types", color: C.iriCyan },
1094
+ { x: 160, y: 640, r: 18, label: "test", color: C.iriGold },
1095
+ ];
1096
+ const edges: [number, number][] = [
1097
+ [0, 1], [0, 2], [2, 3], [2, 4], [2, 5], [5, 6], [4, 7], [5, 8], [1, 5], [3, 4], [6, 7],
1098
+ ];
1099
+
1100
+ return (
1101
+ <svg
1102
+ width={760}
1103
+ height={760}
1104
+ viewBox="0 0 760 760"
1105
+ style={{ position: "absolute", inset: 0 }}
1106
+ >
1107
+ <defs>
1108
+ <linearGradient id="edge-grad" x1="0" x2="1">
1109
+ <stop offset="0" stopColor={C.iriCyan} stopOpacity="0.7" />
1110
+ <stop offset="0.5" stopColor={C.iriViolet} stopOpacity="0.9" />
1111
+ <stop offset="1" stopColor={C.iriRose} stopOpacity="0.7" />
1112
+ </linearGradient>
1113
+ </defs>
1114
+ {edges.map(([a, b], i) => {
1115
+ const start = startFrame + 10 + i * 4;
1116
+ const local = frame - start;
1117
+ const drawProgress = interpolate(local, [0, 28], [0, 1], {
1118
+ extrapolateLeft: "clamp",
1119
+ extrapolateRight: "clamp",
1120
+ easing: ease.power3Out,
1121
+ });
1122
+ const x1 = nodes[a].x;
1123
+ const y1 = nodes[a].y;
1124
+ const x2 = nodes[b].x;
1125
+ const y2 = nodes[b].y;
1126
+ const midX = x1 + (x2 - x1) * drawProgress;
1127
+ const midY = y1 + (y2 - y1) * drawProgress;
1128
+ return (
1129
+ <line
1130
+ key={i}
1131
+ x1={x1}
1132
+ y1={y1}
1133
+ x2={midX}
1134
+ y2={midY}
1135
+ stroke="url(#edge-grad)"
1136
+ strokeWidth={2}
1137
+ strokeLinecap="round"
1138
+ opacity={drawProgress > 0 ? 0.8 : 0}
1139
+ />
1140
+ );
1141
+ })}
1142
+ {nodes.map((n, i) => {
1143
+ const start = startFrame + i * 5;
1144
+ const local = frame - start;
1145
+ const scale = spring({
1146
+ frame: local,
1147
+ fps: 30,
1148
+ config: { damping: 11, stiffness: 130 },
1149
+ from: 0,
1150
+ to: 1,
1151
+ });
1152
+ const pulse = 1 + Math.sin((frame + i * 12) * 0.08) * 0.04;
1153
+ return (
1154
+ <g key={i}>
1155
+ {/* Halo */}
1156
+ <circle
1157
+ cx={n.x}
1158
+ cy={n.y}
1159
+ r={n.r * 2.2 * scale * pulse}
1160
+ fill={n.color}
1161
+ opacity={0.18}
1162
+ />
1163
+ {/* Core */}
1164
+ <circle
1165
+ cx={n.x}
1166
+ cy={n.y}
1167
+ r={n.r * scale * pulse}
1168
+ fill="white"
1169
+ stroke={n.color}
1170
+ strokeWidth={3}
1171
+ />
1172
+ {/* Label */}
1173
+ <text
1174
+ x={n.x}
1175
+ y={n.y + n.r + 26}
1176
+ textAnchor="middle"
1177
+ fontFamily={MONO}
1178
+ fontSize={14}
1179
+ fill={C.inkMuted}
1180
+ opacity={scale}
1181
+ style={{ letterSpacing: "0.1em", textTransform: "uppercase" }}
1182
+ >
1183
+ {n.label}
1184
+ </text>
1185
+ </g>
1186
+ );
1187
+ })}
1188
+ </svg>
1189
+ );
1190
+ };
1191
+
1192
+ const MechanismScene: React.FC = () => {
1193
+ const frame = useCurrentFrame();
1194
+ const local = frame - BEAT.mechanism;
1195
+
1196
+ // GitHub repo card with star counter — first 4s
1197
+ // Then graph nodes build — next 6s
1198
+ const cardOpacity = interpolate(local, [0, 24], [0, 1], { extrapolateRight: "clamp" });
1199
+ const cardY = interpolate(local, [0, 30], [40, 0], {
1200
+ extrapolateRight: "clamp",
1201
+ easing: ease.power3Out,
1202
+ });
1203
+ const cardExitOpacity = interpolate(local, [180, 220], [1, 0], {
1204
+ extrapolateLeft: "clamp",
1205
+ extrapolateRight: "clamp",
1206
+ });
1207
+ const cardExitY = interpolate(local, [180, 220], [0, -40], {
1208
+ extrapolateLeft: "clamp",
1209
+ extrapolateRight: "clamp",
1210
+ easing: ease.power3Out,
1211
+ });
1212
+
1213
+ const graphInOpacity = interpolate(local, [180, 220], [0, 1], {
1214
+ extrapolateLeft: "clamp",
1215
+ extrapolateRight: "clamp",
1216
+ });
1217
+
1218
+ return (
1219
+ <AbsoluteFill>
1220
+ {/* Eyebrow */}
1221
+ <div
1222
+ style={{
1223
+ position: "absolute",
1224
+ top: SAFE_TOP + 24,
1225
+ left: 0,
1226
+ right: 0,
1227
+ display: "flex",
1228
+ justifyContent: "center",
1229
+ opacity: interpolate(local, [0, 20], [0, 1], { extrapolateRight: "clamp" }),
1230
+ }}
1231
+ >
1232
+ <EyebrowPill>03 — what it does</EyebrowPill>
1233
+ </div>
1234
+
1235
+ {/* GitHub star card */}
1236
+ <div
1237
+ style={{
1238
+ position: "absolute",
1239
+ left: 80,
1240
+ right: 80,
1241
+ top: SAFE_TOP + 130,
1242
+ opacity: cardOpacity * cardExitOpacity,
1243
+ transform: `translateY(${cardY + cardExitY}px)`,
1244
+ willChange: "transform, opacity",
1245
+ }}
1246
+ >
1247
+ <GlassCard radius={32} style={{ padding: 40 }}>
1248
+ <div style={{ display: "flex", alignItems: "center", gap: 22, marginBottom: 20 }}>
1249
+ <div
1250
+ style={{
1251
+ width: 64,
1252
+ height: 64,
1253
+ borderRadius: 18,
1254
+ background: "#0E0E12",
1255
+ display: "flex",
1256
+ alignItems: "center",
1257
+ justifyContent: "center",
1258
+ }}
1259
+ >
1260
+ <Img src={staticFile("captures/your-asset.png" /* REFERENCE-STRIP */)} style={{ width: 38, height: 38, filter: "invert(1)" }} />
1261
+ </div>
1262
+ <div style={{ display: "flex", flexDirection: "column" }}>
1263
+ <div
1264
+ style={{
1265
+ fontFamily: MONO,
1266
+ fontSize: 16,
1267
+ color: C.inkDim,
1268
+ letterSpacing: "0.05em",
1269
+ }}
1270
+ >
1271
+ safishamsi /
1272
+ </div>
1273
+ <div
1274
+ style={{
1275
+ fontFamily: FONT,
1276
+ fontSize: 44,
1277
+ fontWeight: 700,
1278
+ color: C.ink,
1279
+ letterSpacing: "-0.03em",
1280
+ lineHeight: 1,
1281
+ }}
1282
+ >
1283
+ graphify
1284
+ </div>
1285
+ </div>
1286
+ </div>
1287
+
1288
+ {/* Stars row */}
1289
+ <div
1290
+ style={{
1291
+ display: "flex",
1292
+ alignItems: "baseline",
1293
+ gap: 18,
1294
+ marginTop: 12,
1295
+ borderTop: `1px solid ${C.hairline}`,
1296
+ paddingTop: 22,
1297
+ }}
1298
+ >
1299
+ <svg width="46" height="46" viewBox="0 0 24 24" fill={C.iriGold}>
1300
+ <path d="M12 2l2.85 6.34L22 9.27l-5.4 4.69 1.69 7.23L12 17.5l-6.29 3.69L7.4 13.96 2 9.27l7.15-.93z" />
1301
+ </svg>
1302
+ <div
1303
+ style={{
1304
+ fontFamily: MONO,
1305
+ fontSize: 88,
1306
+ fontWeight: 700,
1307
+ color: C.ink,
1308
+ fontVariantNumeric: "tabular-nums",
1309
+ letterSpacing: "-0.04em",
1310
+ lineHeight: 1,
1311
+ }}
1312
+ >
1313
+ <Counter
1314
+ from={0}
1315
+ to={35000}
1316
+ startFrame={BEAT.mechanism + 30}
1317
+ duration={120}
1318
+ format={(n) => Math.round(n).toLocaleString()}
1319
+ />
1320
+ </div>
1321
+ <div
1322
+ style={{
1323
+ fontFamily: FONT,
1324
+ fontSize: 28,
1325
+ fontWeight: 500,
1326
+ color: C.inkMuted,
1327
+ marginLeft: "auto",
1328
+ }}
1329
+ >
1330
+ and counting
1331
+ </div>
1332
+ </div>
1333
+
1334
+ {/* Activity sparkline */}
1335
+ <div style={{ marginTop: 32, display: "flex", gap: 6, alignItems: "flex-end" }}>
1336
+ {Array.from({ length: 28 }).map((_, i) => {
1337
+ const baseHeight = 20 + Math.abs(Math.sin(i * 0.7)) * 60 + (i / 28) * 80;
1338
+ const start = BEAT.mechanism + 60 + i * 3;
1339
+ const lf = frame - start;
1340
+ const h = interpolate(lf, [0, 18], [0, baseHeight], {
1341
+ extrapolateLeft: "clamp",
1342
+ extrapolateRight: "clamp",
1343
+ easing: ease.expoOut,
1344
+ });
1345
+ return (
1346
+ <div
1347
+ key={i}
1348
+ style={{
1349
+ flex: 1,
1350
+ height: h,
1351
+ background:
1352
+ i > 22
1353
+ ? `linear-gradient(180deg, ${C.iriViolet}, ${C.iriCyan})`
1354
+ : `linear-gradient(180deg, ${C.inkSoft}, ${C.inkMuted})`,
1355
+ borderRadius: 4,
1356
+ opacity: 0.85,
1357
+ }}
1358
+ />
1359
+ );
1360
+ })}
1361
+ </div>
1362
+ </GlassCard>
1363
+ </div>
1364
+
1365
+ {/* Knowledge graph (second half of scene) */}
1366
+ <div
1367
+ style={{
1368
+ position: "absolute",
1369
+ left: 160,
1370
+ top: 600,
1371
+ opacity: graphInOpacity,
1372
+ transform: `translateY(${interpolate(local, [180, 220], [40, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: ease.expoOut })}px)`,
1373
+ }}
1374
+ >
1375
+ <KnowledgeGraph startFrame={BEAT.mechanism + 200} />
1376
+ </div>
1377
+
1378
+ {/* Caption under graph */}
1379
+ <div
1380
+ style={{
1381
+ position: "absolute",
1382
+ left: 80,
1383
+ right: 80,
1384
+ top: 1380,
1385
+ textAlign: "center",
1386
+ opacity: interpolate(local, [220, 260], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" }),
1387
+ transform: `translateY(${interpolate(local, [220, 260], [16, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: ease.power3Out })}px)`,
1388
+ }}
1389
+ >
1390
+ <div
1391
+ style={{
1392
+ fontFamily: FONT,
1393
+ fontSize: 56,
1394
+ fontWeight: 700,
1395
+ color: C.ink,
1396
+ letterSpacing: "-0.03em",
1397
+ lineHeight: 1.05,
1398
+ }}
1399
+ >
1400
+ Every function. Every connection.
1401
+ </div>
1402
+ <div
1403
+ style={{
1404
+ fontFamily: FONT,
1405
+ fontSize: 56,
1406
+ fontWeight: 700,
1407
+ color: C.iriViolet,
1408
+ letterSpacing: "-0.03em",
1409
+ lineHeight: 1.05,
1410
+ marginTop: 6,
1411
+ }}
1412
+ >
1413
+ Every architectural decision.
1414
+ </div>
1415
+ </div>
1416
+ </AbsoluteFill>
1417
+ );
1418
+ };
1419
+
1420
+ // ═══════════════════════════════════════════════════════════════
1421
+ // SCENE 5 — CLIP (1020-1320, 10s)
1422
+ // "Now here's the trick. It doesn't re-read files. It navigates the graph."
1423
+ // ═══════════════════════════════════════════════════════════════
1424
+ const ClipScene: React.FC = () => {
1425
+ const frame = useCurrentFrame();
1426
+ const local = frame - BEAT.clip;
1427
+
1428
+ const frameOpacity = interpolate(local, [0, 26], [0, 1], { extrapolateRight: "clamp" });
1429
+ const frameY = interpolate(local, [0, 32], [60, 0], {
1430
+ extrapolateRight: "clamp",
1431
+ easing: ease.expoOut,
1432
+ });
1433
+ const tilt = interpolate(local, [0, 50], [-2, 0], {
1434
+ extrapolateRight: "clamp",
1435
+ easing: ease.power3Out,
1436
+ });
1437
+
1438
+ const clipDuration = 300; // 10s of clip
1439
+
1440
+ return (
1441
+ <AbsoluteFill>
1442
+ {/* Eyebrow */}
1443
+ <div
1444
+ style={{
1445
+ position: "absolute",
1446
+ top: SAFE_TOP + 24,
1447
+ left: 0,
1448
+ right: 0,
1449
+ display: "flex",
1450
+ justifyContent: "center",
1451
+ opacity: interpolate(local, [0, 20], [0, 1], { extrapolateRight: "clamp" }),
1452
+ }}
1453
+ >
1454
+ <EyebrowPill>04 — here's the trick</EyebrowPill>
1455
+ </div>
1456
+
1457
+ {/* Headline */}
1458
+ <div style={{ position: "absolute", left: 80, right: 80, top: SAFE_TOP + 110 }}>
1459
+ <StaggeredWords
1460
+ text="It doesn't re-read files."
1461
+ startFrame={BEAT.clip + 10}
1462
+ fontSize={72}
1463
+ fontWeight={600}
1464
+ color={C.inkMuted}
1465
+ align="left"
1466
+ />
1467
+ <div style={{ height: 6 }} />
1468
+ <StaggeredWords
1469
+ text="It navigates the graph."
1470
+ startFrame={BEAT.clip + 38}
1471
+ fontSize={86}
1472
+ fontWeight={800}
1473
+ color={C.ink}
1474
+ align="left"
1475
+ highlight="navigates"
1476
+ highlightColor={C.iriViolet}
1477
+ />
1478
+ </div>
1479
+
1480
+ {/* Tilted glass laptop frame around clip */}
1481
+ <div
1482
+ style={{
1483
+ position: "absolute",
1484
+ left: 60,
1485
+ right: 60,
1486
+ top: 880,
1487
+ height: 540,
1488
+ opacity: frameOpacity,
1489
+ transform: `translateY(${frameY}px) rotate(${tilt}deg) perspective(1400px) rotateX(4deg)`,
1490
+ willChange: "transform",
1491
+ }}
1492
+ >
1493
+ <div
1494
+ style={{
1495
+ ...glassBase,
1496
+ borderRadius: 28,
1497
+ padding: 14,
1498
+ width: "100%",
1499
+ height: "100%",
1500
+ position: "relative",
1501
+ }}
1502
+ >
1503
+ {/* Window controls */}
1504
+ <div style={{ display: "flex", gap: 8, padding: "4px 6px 12px" }}>
1505
+ <div style={{ width: 12, height: 12, borderRadius: 6, background: "#FF5F57" }} />
1506
+ <div style={{ width: 12, height: 12, borderRadius: 6, background: "#FEBC2E" }} />
1507
+ <div style={{ width: 12, height: 12, borderRadius: 6, background: "#28C840" }} />
1508
+ <div
1509
+ style={{
1510
+ marginLeft: "auto",
1511
+ fontFamily: MONO,
1512
+ fontSize: 13,
1513
+ color: C.inkDim,
1514
+ letterSpacing: "0.05em",
1515
+ }}
1516
+ >
1517
+ /graphify .
1518
+ </div>
1519
+ </div>
1520
+
1521
+ {/* Video — wrap in inner Sequence so first frame isn't frozen.
1522
+ The parent ClipScene only mounts when frame is in [BEAT.clip, BEAT.numbers],
1523
+ so we need a Sequence to reset the video's perceived time. */}
1524
+ <Sequence from={BEAT.clip} durationInFrames={clipDuration}>
1525
+ <div
1526
+ style={{
1527
+ width: "100%",
1528
+ height: "calc(100% - 28px)",
1529
+ borderRadius: 18,
1530
+ overflow: "hidden",
1531
+ background: "#0E0E12",
1532
+ }}
1533
+ >
1534
+ {/* REFERENCE-STRIP: <OffthreadVideo> removed — bring your own clip */}
1535
+ </div>
1536
+ </Sequence>
1537
+ </div>
1538
+ </div>
1539
+ </AbsoluteFill>
1540
+ );
1541
+ };
1542
+
1543
+ // ═══════════════════════════════════════════════════════════════
1544
+ // SCENE 6 — NUMBERS (1320-1500, 6s)
1545
+ // "71× fewer tokens. Your $20 plan does the work of $100."
1546
+ // ═══════════════════════════════════════════════════════════════
1547
+ const NumbersScene: React.FC = () => {
1548
+ const frame = useCurrentFrame();
1549
+ const local = frame - BEAT.numbers;
1550
+
1551
+ const numScale = spring({
1552
+ frame: local,
1553
+ fps: 30,
1554
+ config: { damping: 12, stiffness: 100 },
1555
+ from: 0.6,
1556
+ to: 1,
1557
+ });
1558
+ const numOpacity = interpolate(local, [0, 18], [0, 1], { extrapolateRight: "clamp" });
1559
+
1560
+ return (
1561
+ <AbsoluteFill>
1562
+ <div
1563
+ style={{
1564
+ position: "absolute",
1565
+ top: SAFE_TOP + 24,
1566
+ left: 0,
1567
+ right: 0,
1568
+ display: "flex",
1569
+ justifyContent: "center",
1570
+ opacity: interpolate(local, [0, 20], [0, 1], { extrapolateRight: "clamp" }),
1571
+ }}
1572
+ >
1573
+ <EyebrowPill>05 — the math</EyebrowPill>
1574
+ </div>
1575
+
1576
+ {/* Massive 71× ticker */}
1577
+ <div
1578
+ style={{
1579
+ position: "absolute",
1580
+ left: 0,
1581
+ right: 0,
1582
+ top: SAFE_TOP + 160,
1583
+ textAlign: "center",
1584
+ opacity: numOpacity,
1585
+ transform: `scale(${numScale})`,
1586
+ willChange: "transform",
1587
+ }}
1588
+ >
1589
+ <div
1590
+ style={{
1591
+ fontFamily: MONO,
1592
+ fontSize: 480,
1593
+ fontWeight: 700,
1594
+ color: C.ink,
1595
+ letterSpacing: "-0.08em",
1596
+ lineHeight: 0.85,
1597
+ fontVariantNumeric: "tabular-nums",
1598
+ display: "flex",
1599
+ justifyContent: "center",
1600
+ alignItems: "baseline",
1601
+ }}
1602
+ >
1603
+ <Counter
1604
+ from={0}
1605
+ to={71}
1606
+ startFrame={BEAT.numbers + 10}
1607
+ duration={70}
1608
+ format={(n) => Math.round(n).toString()}
1609
+ easing={ease.expoOut}
1610
+ />
1611
+ <span
1612
+ style={{
1613
+ fontSize: 280,
1614
+ fontWeight: 500,
1615
+ color: C.iriViolet,
1616
+ marginLeft: 12,
1617
+ }}
1618
+ >
1619
+ ×
1620
+ </span>
1621
+ </div>
1622
+ <div
1623
+ style={{
1624
+ fontFamily: FONT,
1625
+ fontSize: 48,
1626
+ fontWeight: 600,
1627
+ color: C.inkMuted,
1628
+ letterSpacing: "-0.025em",
1629
+ marginTop: 8,
1630
+ }}
1631
+ >
1632
+ fewer tokens per query
1633
+ </div>
1634
+ </div>
1635
+
1636
+ {/* Plan comparison */}
1637
+ <div
1638
+ style={{
1639
+ position: "absolute",
1640
+ left: 80,
1641
+ right: 80,
1642
+ top: 1240,
1643
+ display: "flex",
1644
+ alignItems: "center",
1645
+ gap: 24,
1646
+ opacity: interpolate(local, [60, 100], [0, 1], { extrapolateRight: "clamp" }),
1647
+ transform: `translateY(${interpolate(local, [60, 100], [30, 0], { extrapolateRight: "clamp", easing: ease.power3Out })}px)`,
1648
+ }}
1649
+ >
1650
+ {/* $20 card */}
1651
+ <GlassCard radius={28} style={{ flex: 1, padding: 30, textAlign: "center" }}>
1652
+ <div
1653
+ style={{
1654
+ fontFamily: MONO,
1655
+ fontSize: 14,
1656
+ color: C.inkDim,
1657
+ letterSpacing: "0.18em",
1658
+ textTransform: "uppercase",
1659
+ }}
1660
+ >
1661
+ you pay
1662
+ </div>
1663
+ <div
1664
+ style={{
1665
+ fontFamily: FONT,
1666
+ fontSize: 96,
1667
+ fontWeight: 800,
1668
+ color: C.ink,
1669
+ letterSpacing: "-0.05em",
1670
+ lineHeight: 1,
1671
+ marginTop: 6,
1672
+ }}
1673
+ >
1674
+ $20
1675
+ </div>
1676
+ <div
1677
+ style={{
1678
+ fontFamily: FONT,
1679
+ fontSize: 18,
1680
+ color: C.inkMuted,
1681
+ marginTop: 4,
1682
+ }}
1683
+ >
1684
+ per month
1685
+ </div>
1686
+ </GlassCard>
1687
+
1688
+ {/* Arrow */}
1689
+ <div
1690
+ style={{
1691
+ fontFamily: MONO,
1692
+ fontSize: 36,
1693
+ color: C.iriViolet,
1694
+ fontWeight: 600,
1695
+ }}
1696
+ >
1697
+
1698
+ </div>
1699
+
1700
+ {/* $100 card */}
1701
+ <div style={{ flex: 1, position: "relative" }}>
1702
+ <IridescentRing size={1} thickness={2} speed={0.8} borderRadius={28} />
1703
+ <div
1704
+ style={{
1705
+ ...glassBase,
1706
+ background: C.glassFillStrong,
1707
+ borderRadius: 28,
1708
+ padding: 30,
1709
+ textAlign: "center",
1710
+ position: "relative",
1711
+ }}
1712
+ >
1713
+ <div
1714
+ style={{
1715
+ fontFamily: MONO,
1716
+ fontSize: 14,
1717
+ color: C.iriViolet,
1718
+ letterSpacing: "0.18em",
1719
+ textTransform: "uppercase",
1720
+ fontWeight: 600,
1721
+ }}
1722
+ >
1723
+ you get
1724
+ </div>
1725
+ <div
1726
+ style={{
1727
+ fontFamily: FONT,
1728
+ fontSize: 96,
1729
+ fontWeight: 800,
1730
+ color: C.ink,
1731
+ letterSpacing: "-0.05em",
1732
+ lineHeight: 1,
1733
+ marginTop: 6,
1734
+ background: `linear-gradient(135deg, ${C.iriCyan}, ${C.iriViolet}, ${C.iriRose})`,
1735
+ WebkitBackgroundClip: "text",
1736
+ WebkitTextFillColor: "transparent",
1737
+ backgroundClip: "text",
1738
+ }}
1739
+ >
1740
+ $100
1741
+ </div>
1742
+ <div
1743
+ style={{
1744
+ fontFamily: FONT,
1745
+ fontSize: 18,
1746
+ color: C.inkMuted,
1747
+ marginTop: 4,
1748
+ }}
1749
+ >
1750
+ of throughput
1751
+ </div>
1752
+ </div>
1753
+ </div>
1754
+ </div>
1755
+ </AbsoluteFill>
1756
+ );
1757
+ };
1758
+
1759
+ // ═══════════════════════════════════════════════════════════════
1760
+ // SCENE 7 — MULTIMODAL (1500-1680, 6s)
1761
+ // "Not just code — PDFs, screenshots, even YouTube videos."
1762
+ // ═══════════════════════════════════════════════════════════════
1763
+ const MultimodalTile: React.FC<{
1764
+ index: number;
1765
+ startFrame: number;
1766
+ label: string;
1767
+ detail: string;
1768
+ iconNode: React.ReactNode;
1769
+ size: { col: number; row: number };
1770
+ position: { col: number; row: number };
1771
+ accent: string;
1772
+ }> = ({ index, startFrame, label, detail, iconNode, size, position, accent }) => {
1773
+ const frame = useCurrentFrame();
1774
+ const local = frame - startFrame - index * 6;
1775
+ const opacity = interpolate(local, [0, 24], [0, 1], {
1776
+ extrapolateLeft: "clamp",
1777
+ extrapolateRight: "clamp",
1778
+ });
1779
+ const scale = spring({
1780
+ frame: local,
1781
+ fps: 30,
1782
+ config: { damping: 12, stiffness: 110 },
1783
+ from: 0.85,
1784
+ to: 1,
1785
+ });
1786
+ // Perpetual micro-pulse
1787
+ const pulse = 1 + Math.sin((frame + index * 30) * 0.04) * 0.012;
1788
+
1789
+ return (
1790
+ <div
1791
+ style={{
1792
+ gridColumn: `${position.col} / span ${size.col}`,
1793
+ gridRow: `${position.row} / span ${size.row}`,
1794
+ opacity,
1795
+ transform: `scale(${scale * pulse})`,
1796
+ willChange: "transform",
1797
+ }}
1798
+ >
1799
+ <GlassCard
1800
+ radius={28}
1801
+ style={{
1802
+ width: "100%",
1803
+ height: "100%",
1804
+ padding: 28,
1805
+ display: "flex",
1806
+ flexDirection: "column",
1807
+ justifyContent: "space-between",
1808
+ position: "relative",
1809
+ overflow: "hidden",
1810
+ }}
1811
+ >
1812
+ {/* Top-right accent dot */}
1813
+ <div
1814
+ style={{
1815
+ position: "absolute",
1816
+ top: 22,
1817
+ right: 22,
1818
+ width: 10,
1819
+ height: 10,
1820
+ borderRadius: 5,
1821
+ background: accent,
1822
+ boxShadow: `0 0 18px ${accent}`,
1823
+ }}
1824
+ />
1825
+ <div
1826
+ style={{
1827
+ width: 56,
1828
+ height: 56,
1829
+ borderRadius: 16,
1830
+ background: "rgba(255,255,255,0.65)",
1831
+ border: `1px solid ${C.hairline}`,
1832
+ display: "flex",
1833
+ alignItems: "center",
1834
+ justifyContent: "center",
1835
+ }}
1836
+ >
1837
+ {iconNode}
1838
+ </div>
1839
+ <div>
1840
+ <div
1841
+ style={{
1842
+ fontFamily: FONT,
1843
+ fontSize: 32,
1844
+ fontWeight: 700,
1845
+ color: C.ink,
1846
+ letterSpacing: "-0.02em",
1847
+ }}
1848
+ >
1849
+ {label}
1850
+ </div>
1851
+ <div
1852
+ style={{
1853
+ fontFamily: MONO,
1854
+ fontSize: 14,
1855
+ color: C.inkDim,
1856
+ letterSpacing: "0.05em",
1857
+ marginTop: 4,
1858
+ }}
1859
+ >
1860
+ {detail}
1861
+ </div>
1862
+ </div>
1863
+ </GlassCard>
1864
+ </div>
1865
+ );
1866
+ };
1867
+
1868
+ const MultimodalScene: React.FC = () => {
1869
+ const frame = useCurrentFrame();
1870
+ const local = frame - BEAT.multimodal;
1871
+
1872
+ return (
1873
+ <AbsoluteFill>
1874
+ <div
1875
+ style={{
1876
+ position: "absolute",
1877
+ top: SAFE_TOP + 24,
1878
+ left: 0,
1879
+ right: 0,
1880
+ display: "flex",
1881
+ justifyContent: "center",
1882
+ opacity: interpolate(local, [0, 20], [0, 1], { extrapolateRight: "clamp" }),
1883
+ }}
1884
+ >
1885
+ <EyebrowPill>06 — beyond code</EyebrowPill>
1886
+ </div>
1887
+
1888
+ {/* Headline */}
1889
+ <div style={{ position: "absolute", left: 80, right: 80, top: SAFE_TOP + 110 }}>
1890
+ <StaggeredWords
1891
+ text="Not just code."
1892
+ startFrame={BEAT.multimodal + 10}
1893
+ fontSize={92}
1894
+ fontWeight={800}
1895
+ color={C.ink}
1896
+ align="left"
1897
+ letterSpacing="-0.04em"
1898
+ />
1899
+ <div style={{ height: 4 }} />
1900
+ <StaggeredWords
1901
+ text="Drop in anything."
1902
+ startFrame={BEAT.multimodal + 30}
1903
+ fontSize={68}
1904
+ fontWeight={500}
1905
+ color={C.inkMuted}
1906
+ align="left"
1907
+ />
1908
+ </div>
1909
+
1910
+ {/* Bento 2.0 — asymmetric grid */}
1911
+ <div
1912
+ style={{
1913
+ position: "absolute",
1914
+ left: 64,
1915
+ right: 64,
1916
+ top: 880,
1917
+ height: 580,
1918
+ display: "grid",
1919
+ gridTemplateColumns: "repeat(6, 1fr)",
1920
+ gridTemplateRows: "repeat(4, 1fr)",
1921
+ gap: 18,
1922
+ }}
1923
+ >
1924
+ <MultimodalTile
1925
+ index={0}
1926
+ startFrame={BEAT.multimodal + 30}
1927
+ size={{ col: 4, row: 2 }}
1928
+ position={{ col: 1, row: 1 }}
1929
+ label="PDFs & papers"
1930
+ detail="citation mining"
1931
+ accent={C.iriRose}
1932
+ iconNode={
1933
+ <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke={C.ink} strokeWidth="1.5">
1934
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
1935
+ <polyline points="14 2 14 8 20 8" />
1936
+ <line x1="9" y1="14" x2="15" y2="14" />
1937
+ <line x1="9" y1="17" x2="13" y2="17" />
1938
+ </svg>
1939
+ }
1940
+ />
1941
+ <MultimodalTile
1942
+ index={1}
1943
+ startFrame={BEAT.multimodal + 30}
1944
+ size={{ col: 2, row: 2 }}
1945
+ position={{ col: 5, row: 1 }}
1946
+ label="Screenshots"
1947
+ detail="vision parsing"
1948
+ accent={C.iriCyan}
1949
+ iconNode={
1950
+ <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke={C.ink} strokeWidth="1.5">
1951
+ <rect x="3" y="3" width="18" height="18" rx="3" />
1952
+ <circle cx="9" cy="9" r="2" />
1953
+ <path d="M21 15l-5-5L5 21" />
1954
+ </svg>
1955
+ }
1956
+ />
1957
+ <MultimodalTile
1958
+ index={2}
1959
+ startFrame={BEAT.multimodal + 30}
1960
+ size={{ col: 2, row: 2 }}
1961
+ position={{ col: 1, row: 3 }}
1962
+ label="YouTube"
1963
+ detail="auto-transcribe"
1964
+ accent={C.iriViolet}
1965
+ iconNode={
1966
+ <svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke={C.ink} strokeWidth="1.5">
1967
+ <rect x="2" y="5" width="20" height="14" rx="3" />
1968
+ <polygon points="10 9 15 12 10 15" fill={C.ink} />
1969
+ </svg>
1970
+ }
1971
+ />
1972
+ <MultimodalTile
1973
+ index={3}
1974
+ startFrame={BEAT.multimodal + 30}
1975
+ size={{ col: 4, row: 2 }}
1976
+ position={{ col: 3, row: 3 }}
1977
+ label="Code · 13 languages"
1978
+ detail="tree-sitter AST"
1979
+ accent={C.iriGold}
1980
+ iconNode={
1981
+ <svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke={C.ink} strokeWidth="1.5">
1982
+ <polyline points="16 18 22 12 16 6" />
1983
+ <polyline points="8 6 2 12 8 18" />
1984
+ <line x1="13" y1="4" x2="11" y2="20" />
1985
+ </svg>
1986
+ }
1987
+ />
1988
+ </div>
1989
+ </AbsoluteFill>
1990
+ );
1991
+ };
1992
+
1993
+ // ═══════════════════════════════════════════════════════════════
1994
+ // SCENE 8 — COMPATIBILITY (1680-1830, 5s)
1995
+ // "Works with Claude Code, Cursor, Codex... every assistant."
1996
+ // ═══════════════════════════════════════════════════════════════
1997
+ const CompatPill: React.FC<{
1998
+ label: string;
1999
+ startFrame: number;
2000
+ index: number;
2001
+ iconNode: React.ReactNode;
2002
+ }> = ({ label, startFrame, index, iconNode }) => {
2003
+ const frame = useCurrentFrame();
2004
+ const local = frame - startFrame - index * 7;
2005
+ const op = interpolate(local, [0, 22], [0, 1], {
2006
+ extrapolateLeft: "clamp",
2007
+ extrapolateRight: "clamp",
2008
+ });
2009
+ const scale = spring({
2010
+ frame: local,
2011
+ fps: 30,
2012
+ config: { damping: 11, stiffness: 130 },
2013
+ from: 0.7,
2014
+ to: 1,
2015
+ });
2016
+ // Magnetic float
2017
+ const floatY = Math.sin((frame + index * 24) * 0.04) * 6;
2018
+
2019
+ return (
2020
+ <div
2021
+ style={{
2022
+ ...glassBase,
2023
+ borderRadius: 9999,
2024
+ padding: "20px 32px",
2025
+ display: "inline-flex",
2026
+ alignItems: "center",
2027
+ gap: 16,
2028
+ opacity: op,
2029
+ transform: `scale(${scale}) translateY(${floatY}px)`,
2030
+ willChange: "transform",
2031
+ }}
2032
+ >
2033
+ <div
2034
+ style={{
2035
+ width: 36,
2036
+ height: 36,
2037
+ borderRadius: 10,
2038
+ background: "rgba(255,255,255,0.7)",
2039
+ border: `1px solid ${C.hairline}`,
2040
+ display: "flex",
2041
+ alignItems: "center",
2042
+ justifyContent: "center",
2043
+ }}
2044
+ >
2045
+ {iconNode}
2046
+ </div>
2047
+ <div
2048
+ style={{
2049
+ fontFamily: FONT,
2050
+ fontSize: 30,
2051
+ fontWeight: 600,
2052
+ color: C.ink,
2053
+ letterSpacing: "-0.02em",
2054
+ }}
2055
+ >
2056
+ {label}
2057
+ </div>
2058
+ </div>
2059
+ );
2060
+ };
2061
+
2062
+ const CompatScene: React.FC = () => {
2063
+ const frame = useCurrentFrame();
2064
+ const local = frame - BEAT.compat;
2065
+
2066
+ const items = [
2067
+ {
2068
+ label: "Claude Code",
2069
+ icon: (
2070
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="#D4663A">
2071
+ <path d="M12 2 L21 7 V17 L12 22 L3 17 V7 Z" />
2072
+ </svg>
2073
+ ),
2074
+ },
2075
+ {
2076
+ label: "Cursor",
2077
+ icon: (
2078
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke={C.ink} strokeWidth="2">
2079
+ <path d="M3 3 L21 12 L13 14 L11 22 Z" fill={C.ink} />
2080
+ </svg>
2081
+ ),
2082
+ },
2083
+ {
2084
+ label: "Codex",
2085
+ icon: (
2086
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke={C.ink} strokeWidth="1.6">
2087
+ <polyline points="16 18 22 12 16 6" />
2088
+ <polyline points="8 6 2 12 8 18" />
2089
+ </svg>
2090
+ ),
2091
+ },
2092
+ {
2093
+ label: "ChatGPT",
2094
+ icon: (
2095
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="#10A37F">
2096
+ <circle cx="12" cy="12" r="9" />
2097
+ </svg>
2098
+ ),
2099
+ },
2100
+ {
2101
+ label: "Gemini",
2102
+ icon: (
2103
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none">
2104
+ <path d="M12 2 L14 10 L22 12 L14 14 L12 22 L10 14 L2 12 L10 10 Z" fill={C.iriCyan} />
2105
+ </svg>
2106
+ ),
2107
+ },
2108
+ ];
2109
+
2110
+ return (
2111
+ <AbsoluteFill>
2112
+ <div
2113
+ style={{
2114
+ position: "absolute",
2115
+ top: SAFE_TOP + 24,
2116
+ left: 0,
2117
+ right: 0,
2118
+ display: "flex",
2119
+ justifyContent: "center",
2120
+ opacity: interpolate(local, [0, 20], [0, 1], { extrapolateRight: "clamp" }),
2121
+ }}
2122
+ >
2123
+ <EyebrowPill>07 — universal</EyebrowPill>
2124
+ </div>
2125
+
2126
+ {/* Headline */}
2127
+ <div style={{ position: "absolute", left: 80, right: 80, top: SAFE_TOP + 130, textAlign: "center" }}>
2128
+ <StaggeredWords
2129
+ text="Works with"
2130
+ startFrame={BEAT.compat + 10}
2131
+ fontSize={64}
2132
+ fontWeight={500}
2133
+ color={C.inkMuted}
2134
+ align="center"
2135
+ />
2136
+ <div style={{ height: 6 }} />
2137
+ <StaggeredWords
2138
+ text="every AI you use."
2139
+ startFrame={BEAT.compat + 28}
2140
+ fontSize={92}
2141
+ fontWeight={800}
2142
+ color={C.ink}
2143
+ align="center"
2144
+ letterSpacing="-0.04em"
2145
+ highlight="every"
2146
+ highlightColor={C.iriViolet}
2147
+ />
2148
+ </div>
2149
+
2150
+ {/* Floating glass pills */}
2151
+ <div
2152
+ style={{
2153
+ position: "absolute",
2154
+ left: 60,
2155
+ right: 60,
2156
+ top: 950,
2157
+ display: "flex",
2158
+ flexWrap: "wrap",
2159
+ gap: 18,
2160
+ justifyContent: "center",
2161
+ alignItems: "center",
2162
+ }}
2163
+ >
2164
+ {items.map((it, i) => (
2165
+ <CompatPill
2166
+ key={i}
2167
+ label={it.label}
2168
+ iconNode={it.icon}
2169
+ startFrame={BEAT.compat + 36}
2170
+ index={i}
2171
+ />
2172
+ ))}
2173
+ </div>
2174
+ </AbsoluteFill>
2175
+ );
2176
+ };
2177
+
2178
+ // ═══════════════════════════════════════════════════════════════
2179
+ // SCENE 9 — CTA (1830-1956, 4.2s)
2180
+ // "Comment AI below. Follow for more."
2181
+ // ═══════════════════════════════════════════════════════════════
2182
+ const CTAScene: React.FC = () => {
2183
+ const frame = useCurrentFrame();
2184
+ const local = frame - BEAT.cta;
2185
+
2186
+ const headlineOp = interpolate(local, [0, 22], [0, 1], { extrapolateRight: "clamp" });
2187
+ const headlineY = interpolate(local, [0, 28], [30, 0], {
2188
+ extrapolateRight: "clamp",
2189
+ easing: ease.expoOut,
2190
+ });
2191
+
2192
+ const pillOp = interpolate(local, [20, 50], [0, 1], { extrapolateRight: "clamp" });
2193
+ const pillScale = spring({
2194
+ frame: local - 20,
2195
+ fps: 30,
2196
+ config: { damping: 12, stiffness: 100 },
2197
+ from: 0.8,
2198
+ to: 1,
2199
+ });
2200
+
2201
+ const handleOp = interpolate(local, [40, 70], [0, 1], { extrapolateRight: "clamp" });
2202
+
2203
+ return (
2204
+ <AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
2205
+ {/* Headline */}
2206
+ <div
2207
+ style={{
2208
+ opacity: headlineOp,
2209
+ transform: `translateY(${headlineY}px)`,
2210
+ textAlign: "center",
2211
+ marginBottom: 28,
2212
+ }}
2213
+ >
2214
+ <div
2215
+ style={{
2216
+ fontFamily: FONT,
2217
+ fontSize: 56,
2218
+ fontWeight: 500,
2219
+ color: C.inkMuted,
2220
+ letterSpacing: "-0.02em",
2221
+ }}
2222
+ >
2223
+ Comment
2224
+ </div>
2225
+ <div
2226
+ style={{
2227
+ fontFamily: FONT,
2228
+ fontSize: 220,
2229
+ fontWeight: 800,
2230
+ color: C.ink,
2231
+ letterSpacing: "-0.07em",
2232
+ lineHeight: 0.9,
2233
+ background: `linear-gradient(135deg, ${C.iriCyan}, ${C.iriViolet}, ${C.iriRose})`,
2234
+ WebkitBackgroundClip: "text",
2235
+ WebkitTextFillColor: "transparent",
2236
+ backgroundClip: "text",
2237
+ }}
2238
+ >
2239
+ "AI"
2240
+ </div>
2241
+ <div
2242
+ style={{
2243
+ fontFamily: FONT,
2244
+ fontSize: 56,
2245
+ fontWeight: 500,
2246
+ color: C.inkMuted,
2247
+ letterSpacing: "-0.02em",
2248
+ marginTop: -10,
2249
+ }}
2250
+ >
2251
+ and I'll DM you the resources.
2252
+ </div>
2253
+ </div>
2254
+
2255
+ {/* Iridescent pill with handle */}
2256
+ <div
2257
+ style={{
2258
+ position: "relative",
2259
+ opacity: pillOp,
2260
+ transform: `scale(${pillScale})`,
2261
+ marginTop: 20,
2262
+ }}
2263
+ >
2264
+ {/* Conic ring around pill */}
2265
+ <div
2266
+ style={{
2267
+ position: "absolute",
2268
+ inset: -3,
2269
+ borderRadius: 9999,
2270
+ background: `conic-gradient(from ${frame * 1.2}deg, ${C.iriCyan}, ${C.iriViolet}, ${C.iriRose}, ${C.iriGold}, ${C.iriCyan})`,
2271
+ filter: "blur(2px)",
2272
+ opacity: 0.8,
2273
+ }}
2274
+ />
2275
+ <div
2276
+ style={{
2277
+ ...glassBase,
2278
+ background: C.glassFillStrong,
2279
+ borderRadius: 9999,
2280
+ padding: "22px 44px",
2281
+ display: "inline-flex",
2282
+ alignItems: "center",
2283
+ gap: 16,
2284
+ position: "relative",
2285
+ opacity: handleOp,
2286
+ }}
2287
+ >
2288
+ {/* Instagram glyph */}
2289
+ <svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke={C.ink} strokeWidth="1.6">
2290
+ <rect x="3" y="3" width="18" height="18" rx="5" />
2291
+ <circle cx="12" cy="12" r="4" />
2292
+ <circle cx="17.5" cy="6.5" r="1" fill={C.ink} />
2293
+ </svg>
2294
+ <div
2295
+ style={{
2296
+ fontFamily: FONT,
2297
+ fontSize: 38,
2298
+ fontWeight: 700,
2299
+ color: C.ink,
2300
+ letterSpacing: "-0.02em",
2301
+ }}
2302
+ >
2303
+ @abhishek.devini
2304
+ </div>
2305
+ </div>
2306
+ </div>
2307
+
2308
+ {/* Follow line */}
2309
+ <div
2310
+ style={{
2311
+ marginTop: 36,
2312
+ opacity: interpolate(local, [60, 90], [0, 1], { extrapolateRight: "clamp" }),
2313
+ fontFamily: MONO,
2314
+ fontSize: 22,
2315
+ color: C.inkMuted,
2316
+ letterSpacing: "0.22em",
2317
+ textTransform: "uppercase",
2318
+ }}
2319
+ >
2320
+ follow for more →
2321
+ </div>
2322
+ </AbsoluteFill>
2323
+ );
2324
+ };
2325
+
2326
+ // ═══════════════════════════════════════════════════════════════
2327
+ // MAIN — orchestrate sequences with audio
2328
+ // ═══════════════════════════════════════════════════════════════
2329
+ export const GraphifyReel: React.FC = () => {
2330
+ const { width, height } = useVideoConfig();
2331
+ const frame = useCurrentFrame();
2332
+
2333
+ return (
2334
+ <AbsoluteFill
2335
+ style={{
2336
+ background: `radial-gradient(ellipse at 30% 20%, ${C.bgWarm} 0%, ${C.bg} 50%, ${C.bgCool} 100%)`,
2337
+ width,
2338
+ height,
2339
+ overflow: "hidden",
2340
+ }}
2341
+ >
2342
+ {/* Perpetual caustic blobs — drift across entire reel */}
2343
+ <CausticBlobs />
2344
+
2345
+ {/* Subtle hairline grid */}
2346
+ <HairlineGrid opacity={0.04} />
2347
+
2348
+ {/* Film-grain noise (fixed, pointer-events none) */}
2349
+ <AbsoluteFill
2350
+ style={{
2351
+ backgroundImage:
2352
+ "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\")",
2353
+ opacity: 0.06,
2354
+ pointerEvents: "none",
2355
+ mixBlendMode: "multiply",
2356
+ }}
2357
+ />
2358
+
2359
+ {/* Scenes — conditional render (no Sequence wrap) so useCurrentFrame() inside
2360
+ each scene returns the GLOBAL frame, which matches our `local = frame - BEAT.x` math. */}
2361
+ {frame >= BEAT.hook && frame < BEAT.problem && <HookScene />}
2362
+ {frame >= BEAT.problem && frame < BEAT.reveal && <ProblemScene />}
2363
+ {frame >= BEAT.reveal && frame < BEAT.mechanism && <RevealScene />}
2364
+ {frame >= BEAT.mechanism && frame < BEAT.clip && <MechanismScene />}
2365
+ {frame >= BEAT.clip && frame < BEAT.numbers && <ClipScene />}
2366
+ {frame >= BEAT.numbers && frame < BEAT.multimodal && <NumbersScene />}
2367
+ {frame >= BEAT.multimodal && frame < BEAT.compat && <MultimodalScene />}
2368
+ {frame >= BEAT.compat && frame < BEAT.cta && <CompatScene />}
2369
+ {frame >= BEAT.cta && frame < BEAT.end && <CTAScene />}
2370
+
2371
+ {/* Bottom safe-zone footer (visible from scene 2 onward) */}
2372
+ <div
2373
+ style={{
2374
+ position: "absolute",
2375
+ bottom: 60,
2376
+ left: 0,
2377
+ right: 0,
2378
+ display: "flex",
2379
+ justifyContent: "center",
2380
+ opacity: interpolate(frame, [180, 220], [0, 1], {
2381
+ extrapolateLeft: "clamp",
2382
+ extrapolateRight: "clamp",
2383
+ }),
2384
+ }}
2385
+ >
2386
+ <div
2387
+ style={{
2388
+ ...glassBase,
2389
+ borderRadius: 9999,
2390
+ padding: "10px 22px",
2391
+ fontFamily: MONO,
2392
+ fontSize: 16,
2393
+ color: C.inkSoft,
2394
+ letterSpacing: "0.2em",
2395
+ textTransform: "uppercase",
2396
+ display: "flex",
2397
+ alignItems: "center",
2398
+ gap: 10,
2399
+ }}
2400
+ >
2401
+ <div
2402
+ style={{
2403
+ width: 6,
2404
+ height: 6,
2405
+ borderRadius: 3,
2406
+ background: C.iriViolet,
2407
+ boxShadow: `0 0 10px ${C.iriViolet}`,
2408
+ }}
2409
+ />
2410
+ @abhishek.devini
2411
+ </div>
2412
+ </div>
2413
+
2414
+ {/* Audio */}
2415
+ {/* REFERENCE-STRIP: <Audio> tag removed — bring your own voiceover */}
2416
+ </AbsoluteFill>
2417
+ );
2418
+ };