@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,2316 @@
1
+ /**
2
+ * REFERENCE — NotebookLMReel (Family: dark)
3
+ *
4
+ * Canonical example of the dark 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/NotebookLMReel.tsx
15
+ * Bundled at: 2026-05-08T18:50:39.534Z
16
+ */
17
+ import {
18
+ useCurrentFrame,
19
+ useVideoConfig,
20
+ interpolate,
21
+ spring,
22
+ Easing,
23
+ AbsoluteFill,
24
+ Img,
25
+ Sequence,
26
+ staticFile,
27
+ } from "remotion";
28
+ import { Audio } from "@remotion/media";
29
+ import { gsap } from "gsap";
30
+ import { ds } from "./designSystem";
31
+
32
+ // ═══════════════════════════════════════════════════════════════
33
+ // CONSTANTS
34
+ // ═══════════════════════════════════════════════════════════════
35
+
36
+ const FONT = ds.font.sans;
37
+ const MONO = ds.font.mono;
38
+
39
+ // GSAP easing bridges (usable inside Remotion's interpolate)
40
+ const e = {
41
+ expoOut: gsap.parseEase("expo.out"),
42
+ expoInOut: gsap.parseEase("expo.inOut"),
43
+ power4Out: gsap.parseEase("power4.out"),
44
+ power3InOut: gsap.parseEase("power3.inOut"),
45
+ backOut: gsap.parseEase("back.out(1.8)"),
46
+ backIn: gsap.parseEase("back.in(1.5)"),
47
+ elasticOut: gsap.parseEase("elastic.out(1, 0.45)"),
48
+ circOut: gsap.parseEase("circ.out"),
49
+ };
50
+
51
+ // Audio is 55.84s. Reel total = 56s = 1680 frames at 30fps
52
+ // Scene timeline (seconds) — aligned to the voiceover rhythm
53
+ const SEGS = [
54
+ { s: 0, e: 3.6, id: "Hook" }, // "99% of Claude users don't know this exists"
55
+ { s: 3.4, e: 7.2, id: "Gift" }, // "gave Claude infinite memory for zero tokens"
56
+ { s: 7.0, e: 13.2, id: "Problem" }, // "every new chat starts from zero, burning tokens"
57
+ { s: 13.0, e: 19.0, id: "Connect" }, // "connect Claude to NotebookLM"
58
+ { s: 18.8, e: 22.8, id: "SecondBrain" },// "second brain that never forgets"
59
+ { s: 22.6, e: 28.8, id: "Benefits1" }, // "persistent memory, lower token costs"
60
+ { s: 28.6, e: 35.2, id: "Benefits2" }, // "infographics, videos, deep research"
61
+ { s: 35.0, e: 41.8, id: "Wrapup" }, // "wrap-up skill stores the whole convo"
62
+ { s: 41.6, e: 47.6, id: "Semantic" }, // "semantic search pulls what it needs"
63
+ { s: 47.4, e: 51.2, id: "Changes" }, // "this one setup changes everything"
64
+ { s: 51.0, e: 55.9, id: "CTA" }, // "Like, Follow, Comment AI"
65
+ ] as const;
66
+
67
+ // Layout — Instagram safe zones: top ~15% & bottom ~22%
68
+ const SAFE_TOP = 300;
69
+ const SAFE_BOTTOM = 1500;
70
+ const W = 1080;
71
+
72
+ // Palette — dark Claude aesthetic + NotebookLM electric blue
73
+ const C = {
74
+ bg: "#07070a",
75
+ bgSurface: "#0f0f14",
76
+ ink: "#f5f2ec",
77
+ ink2: "#a8a59d",
78
+ inkDim: "#6b6863",
79
+ claude: "#D4663A",
80
+ claudeGlow: "rgba(212,102,58,0.45)",
81
+ nlm: "#4F7DF3", // NotebookLM signature blue
82
+ nlmGlow: "rgba(79,125,243,0.5)",
83
+ gold: "#F0C23A",
84
+ danger: "#E8483A",
85
+ success: "#3CCB7F",
86
+ line: "rgba(255,255,255,0.08)",
87
+ lineStrong: "rgba(255,255,255,0.18)",
88
+ };
89
+
90
+ // ═══════════════════════════════════════════════════════════════
91
+ // UTILITIES
92
+ // ═══════════════════════════════════════════════════════════════
93
+
94
+ // Scene opacity helper — fade in / hold / fade out
95
+ const so = (
96
+ frame: number,
97
+ fps: number,
98
+ startS: number,
99
+ endS: number,
100
+ fadeIn = 0.35,
101
+ fadeOut = 0.45,
102
+ ) =>
103
+ interpolate(
104
+ frame,
105
+ [startS * fps, (startS + fadeIn) * fps, (endS - fadeOut) * fps, endS * fps],
106
+ [0, 1, 1, 0],
107
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" },
108
+ );
109
+
110
+ // Local frame since scene start, never negative
111
+ const lf = (frame: number, fps: number, startSec: number) =>
112
+ Math.max(0, frame - startSec * fps);
113
+
114
+ // Tween helper using a GSAP ease — maps localFrame into output range
115
+ const tween = (
116
+ localFrame: number,
117
+ fps: number,
118
+ durationSec: number,
119
+ from: number,
120
+ to: number,
121
+ ease: (t: number) => number = e.expoOut,
122
+ delaySec = 0,
123
+ ) => {
124
+ const start = delaySec * fps;
125
+ const end = start + durationSec * fps;
126
+ return interpolate(localFrame, [start, end], [from, to], {
127
+ easing: ease,
128
+ extrapolateLeft: "clamp",
129
+ extrapolateRight: "clamp",
130
+ });
131
+ };
132
+
133
+ interface SP {
134
+ frame: number;
135
+ fps: number;
136
+ }
137
+
138
+ // ═══════════════════════════════════════════════════════════════
139
+ // BACKGROUND — Deep space with subtle gradient breathing
140
+ // ═══════════════════════════════════════════════════════════════
141
+
142
+ const Background: React.FC<{ frame: number; accent: string }> = ({ frame, accent }) => {
143
+ const pulse = 0.3 + Math.sin(frame * 0.02) * 0.1;
144
+ const drift = (frame * 0.04) % 100;
145
+ return (
146
+ <AbsoluteFill style={{ background: C.bg, overflow: "hidden" }}>
147
+ {/* Vignette wash */}
148
+ <div
149
+ style={{
150
+ position: "absolute",
151
+ inset: 0,
152
+ background: `radial-gradient(ellipse at 50% 45%, ${C.bgSurface} 0%, ${C.bg} 65%, #000 100%)`,
153
+ }}
154
+ />
155
+ {/* Grid lines */}
156
+ <div
157
+ style={{
158
+ position: "absolute",
159
+ inset: 0,
160
+ backgroundImage: `linear-gradient(${C.line} 1px, transparent 1px), linear-gradient(90deg, ${C.line} 1px, transparent 1px)`,
161
+ backgroundSize: "80px 80px",
162
+ backgroundPosition: `0 ${drift}px`,
163
+ opacity: 0.5,
164
+ maskImage: `radial-gradient(ellipse at 50% 45%, #000 0%, transparent 70%)`,
165
+ WebkitMaskImage: `radial-gradient(ellipse at 50% 45%, #000 0%, transparent 70%)`,
166
+ }}
167
+ />
168
+ {/* Accent atmospheric glow */}
169
+ <div
170
+ style={{
171
+ position: "absolute",
172
+ width: 1400,
173
+ height: 1400,
174
+ top: 260,
175
+ left: -160,
176
+ borderRadius: "50%",
177
+ background: `radial-gradient(circle, ${accent} 0%, transparent 55%)`,
178
+ opacity: pulse,
179
+ filter: "blur(80px)",
180
+ }}
181
+ />
182
+ {/* Film grain */}
183
+ <div
184
+ style={{
185
+ position: "absolute",
186
+ inset: 0,
187
+ pointerEvents: "none",
188
+ opacity: 0.18,
189
+ mixBlendMode: "overlay",
190
+ backgroundImage:
191
+ "url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9'/></filter><rect width='100%' height='100%' filter='url(%23n)' opacity='0.6'/></svg>\")",
192
+ }}
193
+ />
194
+ </AbsoluteFill>
195
+ );
196
+ };
197
+
198
+ // ═══════════════════════════════════════════════════════════════
199
+ // REUSABLE ATOMS
200
+ // ═══════════════════════════════════════════════════════════════
201
+
202
+ const ClaudeGlyph: React.FC<{ size: number; glow?: number; rotate?: number }> = ({
203
+ size,
204
+ glow = 0.55,
205
+ rotate = 0,
206
+ }) => (
207
+ <div style={{ position: "relative", width: size, height: size }}>
208
+ <div
209
+ style={{
210
+ position: "absolute",
211
+ inset: -size * 0.25,
212
+ borderRadius: "50%",
213
+ background: `radial-gradient(circle, rgba(212,102,58,${glow}) 0%, transparent 55%)`,
214
+ filter: "blur(24px)",
215
+ }}
216
+ />
217
+ <Img
218
+ src={staticFile("captures/your-asset.png" /* REFERENCE-STRIP */)}
219
+ style={{
220
+ width: size,
221
+ height: size,
222
+ display: "block",
223
+ transform: `rotate(${rotate}deg)`,
224
+ filter: "drop-shadow(0 14px 22px rgba(150,60,30,0.45))",
225
+ }}
226
+ />
227
+ </div>
228
+ );
229
+
230
+ // NotebookLM logo — recreated with the authentic stacked-arc mark
231
+ const NotebookLMGlyph: React.FC<{ size: number; glow?: number; rings?: number }> = ({
232
+ size,
233
+ glow = 0.55,
234
+ rings = 3,
235
+ }) => {
236
+ // Rings radii decreasing
237
+ const stroke = size * 0.09;
238
+ const step = (size / 2 - stroke) / rings;
239
+ const arcs = Array.from({ length: rings }, (_, i) => (size / 2 - stroke) - step * i);
240
+ return (
241
+ <div
242
+ style={{
243
+ position: "relative",
244
+ width: size,
245
+ height: size,
246
+ display: "flex",
247
+ alignItems: "center",
248
+ justifyContent: "center",
249
+ }}
250
+ >
251
+ <div
252
+ style={{
253
+ position: "absolute",
254
+ inset: -size * 0.25,
255
+ borderRadius: "50%",
256
+ background: `radial-gradient(circle, rgba(79,125,243,${glow}) 0%, transparent 55%)`,
257
+ filter: "blur(26px)",
258
+ }}
259
+ />
260
+ <svg width={size} height={size} viewBox="0 0 100 100" style={{ position: "relative" }}>
261
+ <defs>
262
+ <linearGradient id="nlmG" x1="0" y1="0" x2="1" y2="1">
263
+ <stop offset="0" stopColor="#6B9AFF" />
264
+ <stop offset="0.5" stopColor="#4F7DF3" />
265
+ <stop offset="1" stopColor="#E84B6C" />
266
+ </linearGradient>
267
+ </defs>
268
+ {arcs.map((r, i) => (
269
+ <path
270
+ key={i}
271
+ d={`M ${50 - r} 72 A ${r} ${r} 0 0 1 ${50 + r} 72`}
272
+ fill="none"
273
+ stroke="url(#nlmG)"
274
+ strokeWidth={size * 0.055}
275
+ strokeLinecap="round"
276
+ opacity={0.35 + (i / rings) * 0.65}
277
+ />
278
+ ))}
279
+ {/* Base line suggesting a book spine */}
280
+ <rect
281
+ x="22"
282
+ y="74"
283
+ width="56"
284
+ height={size * 0.045}
285
+ rx="2"
286
+ fill="url(#nlmG)"
287
+ />
288
+ </svg>
289
+ </div>
290
+ );
291
+ };
292
+
293
+ // Minimal plug/wire that animates drawing
294
+ const WireConnect: React.FC<{
295
+ from: [number, number];
296
+ to: [number, number];
297
+ progress: number;
298
+ color: string;
299
+ spark?: number;
300
+ width?: number;
301
+ }> = ({ from, to, progress, color, spark = 0, width = 3 }) => {
302
+ const len = Math.hypot(to[0] - from[0], to[1] - from[1]);
303
+ const cx = (from[0] + to[0]) / 2;
304
+ const cy = (from[1] + to[1]) / 2 - 70;
305
+ return (
306
+ <svg
307
+ width={W}
308
+ height={1920}
309
+ viewBox={`0 0 ${W} 1920`}
310
+ style={{ position: "absolute", inset: 0, pointerEvents: "none" }}
311
+ >
312
+ <defs>
313
+ <linearGradient id="wireG" x1="0" y1="0" x2="1" y2="0">
314
+ <stop offset="0" stopColor={C.claude} />
315
+ <stop offset="1" stopColor={C.nlm} />
316
+ </linearGradient>
317
+ <filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
318
+ <feGaussianBlur stdDeviation="4" result="b" />
319
+ <feMerge>
320
+ <feMergeNode in="b" />
321
+ <feMergeNode in="SourceGraphic" />
322
+ </feMerge>
323
+ </filter>
324
+ </defs>
325
+ <path
326
+ d={`M ${from[0]} ${from[1]} Q ${cx} ${cy} ${to[0]} ${to[1]}`}
327
+ fill="none"
328
+ stroke="url(#wireG)"
329
+ strokeWidth={width}
330
+ strokeLinecap="round"
331
+ strokeDasharray={len * 1.2}
332
+ strokeDashoffset={(1 - progress) * len * 1.2}
333
+ filter="url(#glow)"
334
+ />
335
+ {spark > 0 && (
336
+ <circle
337
+ cx={from[0] + (to[0] - from[0]) * progress}
338
+ cy={(from[1] + (to[1] - from[1])) / 2 - 70 * 4 * progress * (1 - progress)}
339
+ r={8 * spark}
340
+ fill={color}
341
+ opacity={spark}
342
+ filter="url(#glow)"
343
+ />
344
+ )}
345
+ </svg>
346
+ );
347
+ };
348
+
349
+ // ═══════════════════════════════════════════════════════════════
350
+ // SCENE 1 — HOOK (99% don't know this exists)
351
+ // ═══════════════════════════════════════════════════════════════
352
+
353
+ const Scene1Hook: React.FC<SP> = ({ frame, fps }) => {
354
+ const lof = lf(frame, fps, SEGS[0].s);
355
+ // Count up 0 → 99
356
+ const raw = tween(lof, fps, 1.4, 0, 99, e.expoOut, 0.15);
357
+ const pct = Math.round(raw);
358
+
359
+ const headY = tween(lof, fps, 0.9, 50, 0, e.expoOut, 0.1);
360
+ const headOp = tween(lof, fps, 0.6, 0, 1, e.expoOut, 0.05);
361
+
362
+ const subScale = tween(lof, fps, 0.9, 0.7, 1, e.backOut, 1.0);
363
+ const subOp = tween(lof, fps, 0.6, 0, 1, e.expoOut, 1.0);
364
+
365
+ const tagY = tween(lof, fps, 0.8, 30, 0, e.expoOut, 1.6);
366
+ const tagOp = tween(lof, fps, 0.6, 0, 1, e.expoOut, 1.6);
367
+
368
+ const highlightP = tween(lof, fps, 1.2, 0, 1, e.expoOut, 2.2);
369
+
370
+ return (
371
+ <AbsoluteFill
372
+ style={{
373
+ justifyContent: "center",
374
+ alignItems: "center",
375
+ paddingTop: SAFE_TOP + 80,
376
+ paddingBottom: 1920 - SAFE_BOTTOM + 80,
377
+ }}
378
+ >
379
+ {/* Small label */}
380
+ <div
381
+ style={{
382
+ transform: `translateY(${tagY}px)`,
383
+ opacity: tagOp,
384
+ padding: "10px 22px",
385
+ borderRadius: 9999,
386
+ border: `1px solid ${C.lineStrong}`,
387
+ background: "rgba(255,255,255,0.03)",
388
+ fontFamily: MONO,
389
+ fontSize: 24,
390
+ letterSpacing: "0.2em",
391
+ color: C.ink2,
392
+ textTransform: "uppercase",
393
+ marginBottom: 36,
394
+ }}
395
+ >
396
+ a secret most miss
397
+ </div>
398
+
399
+ {/* Giant 99% */}
400
+ <div
401
+ style={{
402
+ transform: `translateY(${headY}px)`,
403
+ opacity: headOp,
404
+ fontFamily: FONT,
405
+ color: C.ink,
406
+ fontSize: 440,
407
+ fontWeight: 700,
408
+ letterSpacing: "-0.06em",
409
+ lineHeight: 0.85,
410
+ textAlign: "center",
411
+ textShadow: `0 0 80px rgba(212,102,58,0.35)`,
412
+ }}
413
+ >
414
+ <span style={{ color: C.claude }}>{pct}</span>
415
+ <span style={{ color: C.ink, opacity: 0.85 }}>%</span>
416
+ </div>
417
+
418
+ {/* Subheader */}
419
+ <div
420
+ style={{
421
+ transform: `scale(${subScale})`,
422
+ opacity: subOp,
423
+ marginTop: 32,
424
+ fontFamily: FONT,
425
+ color: C.ink,
426
+ fontSize: 64,
427
+ fontWeight: 600,
428
+ letterSpacing: "-0.03em",
429
+ textAlign: "center",
430
+ lineHeight: 1.05,
431
+ maxWidth: 900,
432
+ }}
433
+ >
434
+ of Claude users have <br />
435
+ <span
436
+ style={{
437
+ background: `linear-gradient(90deg, ${C.claude} 0%, ${C.gold} 100%)`,
438
+ WebkitBackgroundClip: "text",
439
+ backgroundClip: "text",
440
+ WebkitTextFillColor: "transparent",
441
+ position: "relative",
442
+ }}
443
+ >
444
+ no idea this exists
445
+ <span
446
+ style={{
447
+ position: "absolute",
448
+ left: 0,
449
+ bottom: -6,
450
+ height: 4,
451
+ width: `${highlightP * 100}%`,
452
+ background: `linear-gradient(90deg, ${C.claude}, ${C.gold})`,
453
+ borderRadius: 2,
454
+ }}
455
+ />
456
+ </span>
457
+ </div>
458
+
459
+ {/* Dot swarm representing users */}
460
+ <div
461
+ style={{
462
+ marginTop: 56,
463
+ display: "grid",
464
+ gridTemplateColumns: "repeat(20, 18px)",
465
+ gap: 10,
466
+ opacity: tagOp * 0.9,
467
+ }}
468
+ >
469
+ {Array.from({ length: 100 }).map((_, i) => {
470
+ const isLit = i === 42; // the 1%
471
+ const reveal = tween(lof, fps, 1.4, 0, 1, e.expoOut, 0.25 + (i / 100) * 0.5);
472
+ return (
473
+ <div
474
+ key={i}
475
+ style={{
476
+ width: 18,
477
+ height: 18,
478
+ borderRadius: 9999,
479
+ background: isLit ? C.claude : "rgba(255,255,255,0.18)",
480
+ boxShadow: isLit ? `0 0 18px ${C.claudeGlow}` : "none",
481
+ transform: `scale(${reveal})`,
482
+ }}
483
+ />
484
+ );
485
+ })}
486
+ </div>
487
+ </AbsoluteFill>
488
+ );
489
+ };
490
+
491
+ // ═══════════════════════════════════════════════════════════════
492
+ // SCENE 2 — GIFT (infinite memory, zero tokens)
493
+ // ═══════════════════════════════════════════════════════════════
494
+
495
+ const Scene2Gift: React.FC<SP> = ({ frame, fps }) => {
496
+ const lof = lf(frame, fps, SEGS[1].s);
497
+
498
+ const claudeP = tween(lof, fps, 0.8, 0, 1, e.backOut, 0.0);
499
+ const infP = tween(lof, fps, 1.0, 0, 1, e.expoOut, 0.4);
500
+ const badgeP = tween(lof, fps, 0.7, 0, 1, e.backOut, 0.9);
501
+
502
+ // Animated infinity stroke-draw
503
+ const infDraw = infP;
504
+
505
+ // Token counter: starts at 10,000, plunges to 0
506
+ const tokRaw = tween(lof, fps, 1.4, 10000, 0, e.expoOut, 1.2);
507
+ const tokCount = Math.max(0, Math.round(tokRaw / 10) * 10);
508
+
509
+ return (
510
+ <AbsoluteFill
511
+ style={{
512
+ justifyContent: "center",
513
+ alignItems: "center",
514
+ paddingTop: SAFE_TOP,
515
+ paddingBottom: 1920 - SAFE_BOTTOM,
516
+ }}
517
+ >
518
+ {/* Headline */}
519
+ <div
520
+ style={{
521
+ fontFamily: FONT,
522
+ fontSize: 56,
523
+ fontWeight: 600,
524
+ color: C.ink,
525
+ textAlign: "center",
526
+ letterSpacing: "-0.02em",
527
+ opacity: claudeP,
528
+ marginBottom: 28,
529
+ }}
530
+ >
531
+ I gave Claude
532
+ </div>
533
+
534
+ {/* Infinity + Claude combo */}
535
+ <div
536
+ style={{
537
+ display: "flex",
538
+ alignItems: "center",
539
+ justifyContent: "center",
540
+ gap: 28,
541
+ marginBottom: 44,
542
+ }}
543
+ >
544
+ <div style={{ transform: `scale(${claudeP})` }}>
545
+ <ClaudeGlyph size={170} glow={0.7} />
546
+ </div>
547
+ <div style={{ transform: `scale(${infP})`, position: "relative" }}>
548
+ <svg width={260} height={140} viewBox="0 0 260 140">
549
+ <defs>
550
+ <linearGradient id="infG" x1="0" y1="0" x2="1" y2="0">
551
+ <stop offset="0" stopColor={C.claude} />
552
+ <stop offset="1" stopColor={C.nlm} />
553
+ </linearGradient>
554
+ </defs>
555
+ <path
556
+ d="M 35 70 C 35 25 95 25 130 70 C 165 115 225 115 225 70 C 225 25 165 25 130 70 C 95 115 35 115 35 70 Z"
557
+ fill="none"
558
+ stroke="url(#infG)"
559
+ strokeWidth={14}
560
+ strokeLinecap="round"
561
+ strokeDasharray={700}
562
+ strokeDashoffset={(1 - infDraw) * 700}
563
+ style={{
564
+ filter: `drop-shadow(0 0 18px ${C.claudeGlow})`,
565
+ }}
566
+ />
567
+ </svg>
568
+ </div>
569
+ </div>
570
+
571
+ {/* Memory label */}
572
+ <div
573
+ style={{
574
+ fontFamily: FONT,
575
+ fontSize: 86,
576
+ fontWeight: 700,
577
+ color: C.ink,
578
+ letterSpacing: "-0.04em",
579
+ textAlign: "center",
580
+ opacity: infP,
581
+ lineHeight: 1,
582
+ marginBottom: 48,
583
+ }}
584
+ >
585
+ infinite memory
586
+ </div>
587
+
588
+ {/* Cost badge — dramatic zero */}
589
+ <div
590
+ style={{
591
+ transform: `scale(${badgeP})`,
592
+ padding: "22px 38px",
593
+ borderRadius: 28,
594
+ border: `1px solid ${C.line}`,
595
+ background: "rgba(60,203,127,0.08)",
596
+ display: "flex",
597
+ alignItems: "center",
598
+ gap: 18,
599
+ boxShadow: `0 20px 60px -16px rgba(60,203,127,0.25), inset 0 1px 0 rgba(255,255,255,0.08)`,
600
+ }}
601
+ >
602
+ <div
603
+ style={{
604
+ width: 10,
605
+ height: 10,
606
+ borderRadius: 9999,
607
+ background: C.success,
608
+ boxShadow: `0 0 14px ${C.success}`,
609
+ }}
610
+ />
611
+ <div
612
+ style={{
613
+ fontFamily: MONO,
614
+ fontSize: 28,
615
+ color: C.ink2,
616
+ letterSpacing: "0.15em",
617
+ textTransform: "uppercase",
618
+ }}
619
+ >
620
+ cost ·
621
+ </div>
622
+ <div
623
+ style={{
624
+ fontFamily: MONO,
625
+ fontSize: 54,
626
+ color: C.success,
627
+ fontWeight: 600,
628
+ minWidth: 220,
629
+ }}
630
+ >
631
+ {tokCount.toLocaleString()}
632
+ <span style={{ color: C.ink2, marginLeft: 10, fontSize: 24 }}>tokens</span>
633
+ </div>
634
+ </div>
635
+ </AbsoluteFill>
636
+ );
637
+ };
638
+
639
+ // ═══════════════════════════════════════════════════════════════
640
+ // SCENE 3 — PROBLEM (every chat resets, tokens burning)
641
+ // ═══════════════════════════════════════════════════════════════
642
+
643
+ const Scene3Problem: React.FC<SP> = ({ frame, fps }) => {
644
+ const lof = lf(frame, fps, SEGS[2].s);
645
+
646
+ const headP = tween(lof, fps, 0.7, 0, 1, e.expoOut, 0);
647
+
648
+ // Three chat cards, each cycling through reset animations
649
+ const cards = [0, 1, 2];
650
+ const cardDelays = [0.3, 0.7, 1.1];
651
+
652
+ // Token burn meter — fills then crashes
653
+ const burnFill = interpolate(
654
+ lof,
655
+ [3.0 * fps, 4.0 * fps, 4.4 * fps, 5.4 * fps],
656
+ [0, 1, 1, 0],
657
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" },
658
+ );
659
+ const burnCrash = interpolate(lof, [4.2 * fps, 5.2 * fps], [0, 1], {
660
+ extrapolateLeft: "clamp",
661
+ extrapolateRight: "clamp",
662
+ });
663
+
664
+ return (
665
+ <AbsoluteFill
666
+ style={{
667
+ paddingTop: SAFE_TOP,
668
+ paddingBottom: 1920 - SAFE_BOTTOM,
669
+ paddingLeft: 80,
670
+ paddingRight: 80,
671
+ }}
672
+ >
673
+ {/* Header */}
674
+ <div
675
+ style={{
676
+ opacity: headP,
677
+ fontFamily: FONT,
678
+ color: C.ink,
679
+ fontSize: 72,
680
+ fontWeight: 700,
681
+ letterSpacing: "-0.03em",
682
+ lineHeight: 1,
683
+ marginTop: 40,
684
+ marginBottom: 60,
685
+ }}
686
+ >
687
+ Every new chat
688
+ <br />
689
+ <span style={{ color: C.danger }}>starts from zero.</span>
690
+ </div>
691
+
692
+ {/* Stacked chat cards */}
693
+ <div style={{ display: "flex", flexDirection: "column", gap: 22, marginBottom: 60 }}>
694
+ {cards.map((i) => {
695
+ const delay = cardDelays[i];
696
+ const enter = tween(lof, fps, 0.6, 0, 1, e.backOut, delay);
697
+ const wipe = tween(lof, fps, 0.8, 0, 1, e.expoInOut, delay + 1.6);
698
+ const msgs = [
699
+ ["Hi Claude, remember our project…", "No prior context found."],
700
+ ["Continue from last session?", "Starting fresh."],
701
+ ["You already know my stack?", "Tell me again."],
702
+ ][i];
703
+ return (
704
+ <div
705
+ key={i}
706
+ style={{
707
+ transform: `translateX(${(1 - enter) * -80}px) scale(${0.95 + enter * 0.05})`,
708
+ opacity: enter,
709
+ background: "rgba(255,255,255,0.03)",
710
+ border: `1px solid ${C.line}`,
711
+ borderRadius: 20,
712
+ padding: "20px 26px",
713
+ display: "flex",
714
+ alignItems: "center",
715
+ gap: 22,
716
+ position: "relative",
717
+ overflow: "hidden",
718
+ }}
719
+ >
720
+ {/* User dot */}
721
+ <div
722
+ style={{
723
+ width: 44,
724
+ height: 44,
725
+ flexShrink: 0,
726
+ borderRadius: 9999,
727
+ background: `linear-gradient(135deg, ${C.ink2}, ${C.inkDim})`,
728
+ }}
729
+ />
730
+ <div style={{ flex: 1 }}>
731
+ <div
732
+ style={{
733
+ fontFamily: FONT,
734
+ fontSize: 26,
735
+ color: C.ink,
736
+ marginBottom: 6,
737
+ opacity: 1 - wipe * 0.85,
738
+ }}
739
+ >
740
+ {msgs[0]}
741
+ </div>
742
+ <div
743
+ style={{
744
+ fontFamily: MONO,
745
+ fontSize: 22,
746
+ color: C.danger,
747
+ letterSpacing: "0.02em",
748
+ opacity: wipe,
749
+ }}
750
+ >
751
+ {msgs[1]}
752
+ </div>
753
+ </div>
754
+ {/* Reset sweep */}
755
+ <div
756
+ style={{
757
+ position: "absolute",
758
+ top: 0,
759
+ bottom: 0,
760
+ left: `${wipe * 100 - 8}%`,
761
+ width: 180,
762
+ background: `linear-gradient(90deg, transparent, rgba(232,72,58,0.35), transparent)`,
763
+ pointerEvents: "none",
764
+ }}
765
+ />
766
+ </div>
767
+ );
768
+ })}
769
+ </div>
770
+
771
+ {/* Token burn meter */}
772
+ <div
773
+ style={{
774
+ padding: "22px 28px",
775
+ background: "rgba(255,255,255,0.03)",
776
+ border: `1px solid ${C.line}`,
777
+ borderRadius: 20,
778
+ opacity: headP,
779
+ }}
780
+ >
781
+ <div
782
+ style={{
783
+ display: "flex",
784
+ justifyContent: "space-between",
785
+ alignItems: "center",
786
+ marginBottom: 16,
787
+ }}
788
+ >
789
+ <div
790
+ style={{
791
+ fontFamily: MONO,
792
+ fontSize: 22,
793
+ color: C.ink2,
794
+ letterSpacing: "0.15em",
795
+ textTransform: "uppercase",
796
+ }}
797
+ >
798
+ tokens burning
799
+ </div>
800
+ <div
801
+ style={{
802
+ fontFamily: MONO,
803
+ fontSize: 34,
804
+ fontWeight: 600,
805
+ color: burnCrash > 0.5 ? C.danger : C.ink,
806
+ }}
807
+ >
808
+ {Math.round((1 - burnCrash) * 82_500 * burnFill).toLocaleString()}
809
+ </div>
810
+ </div>
811
+ <div
812
+ style={{
813
+ position: "relative",
814
+ height: 18,
815
+ background: "rgba(255,255,255,0.05)",
816
+ borderRadius: 9999,
817
+ overflow: "hidden",
818
+ }}
819
+ >
820
+ <div
821
+ style={{
822
+ position: "absolute",
823
+ left: 0,
824
+ top: 0,
825
+ bottom: 0,
826
+ width: `${burnFill * 100}%`,
827
+ background: `linear-gradient(90deg, ${C.gold}, ${C.danger})`,
828
+ boxShadow: `0 0 20px ${C.danger}`,
829
+ transition: "none",
830
+ }}
831
+ />
832
+ {/* Flame flickers */}
833
+ {burnFill > 0.15 && (
834
+ <div
835
+ style={{
836
+ position: "absolute",
837
+ top: -12,
838
+ left: `${burnFill * 100 - 6}%`,
839
+ width: 28,
840
+ height: 42,
841
+ borderRadius: "50% 50% 30% 30%",
842
+ background: `radial-gradient(circle at 50% 80%, ${C.gold}, ${C.danger})`,
843
+ filter: "blur(3px)",
844
+ opacity: 0.85 - burnCrash,
845
+ transform: `scaleY(${1 + Math.sin(lof * 0.4) * 0.15})`,
846
+ }}
847
+ />
848
+ )}
849
+ </div>
850
+ </div>
851
+ </AbsoluteFill>
852
+ );
853
+ };
854
+
855
+ // ═══════════════════════════════════════════════════════════════
856
+ // SCENE 4 — CONNECT (Claude × NotebookLM)
857
+ // ═══════════════════════════════════════════════════════════════
858
+
859
+ const Scene4Connect: React.FC<SP> = ({ frame, fps }) => {
860
+ const lof = lf(frame, fps, SEGS[3].s);
861
+
862
+ const labelOp = tween(lof, fps, 0.7, 0, 1, e.expoOut, 0);
863
+ const cP = tween(lof, fps, 0.8, 0, 1, e.backOut, 0.3);
864
+ const nP = tween(lof, fps, 0.8, 0, 1, e.backOut, 0.8);
865
+ const wire = tween(lof, fps, 1.2, 0, 1, e.expoInOut, 1.6);
866
+ const spark = interpolate(lof, [2.8 * fps, 3.2 * fps, 3.6 * fps, 4.0 * fps], [0, 1, 1, 0], {
867
+ extrapolateLeft: "clamp",
868
+ extrapolateRight: "clamp",
869
+ });
870
+ const flash = interpolate(lof, [2.9 * fps, 3.1 * fps, 3.6 * fps], [0, 1, 0], {
871
+ extrapolateLeft: "clamp",
872
+ extrapolateRight: "clamp",
873
+ });
874
+ const badgeP = tween(lof, fps, 0.7, 0, 1, e.backOut, 3.6);
875
+
876
+ return (
877
+ <AbsoluteFill
878
+ style={{
879
+ paddingTop: SAFE_TOP,
880
+ paddingBottom: 1920 - SAFE_BOTTOM,
881
+ }}
882
+ >
883
+ {/* Top eyebrow */}
884
+ <div
885
+ style={{
886
+ opacity: labelOp,
887
+ textAlign: "center",
888
+ fontFamily: MONO,
889
+ color: C.ink2,
890
+ letterSpacing: "0.25em",
891
+ fontSize: 22,
892
+ textTransform: "uppercase",
893
+ marginTop: 60,
894
+ }}
895
+ >
896
+ the trick
897
+ </div>
898
+ <div
899
+ style={{
900
+ opacity: labelOp,
901
+ textAlign: "center",
902
+ fontFamily: FONT,
903
+ color: C.ink,
904
+ fontWeight: 700,
905
+ fontSize: 72,
906
+ letterSpacing: "-0.035em",
907
+ lineHeight: 1.02,
908
+ marginTop: 20,
909
+ }}
910
+ >
911
+ Plug Claude into<br />NotebookLM.
912
+ </div>
913
+
914
+ {/* Side-by-side logos with connecting wire */}
915
+ <div
916
+ style={{
917
+ position: "relative",
918
+ flex: 1,
919
+ display: "flex",
920
+ alignItems: "center",
921
+ justifyContent: "space-between",
922
+ padding: "0 80px",
923
+ marginTop: 60,
924
+ }}
925
+ >
926
+ {/* Claude card */}
927
+ <div
928
+ style={{
929
+ transform: `scale(${cP}) translateX(${(1 - cP) * -60}px)`,
930
+ opacity: cP,
931
+ padding: 34,
932
+ borderRadius: 28,
933
+ background: "rgba(212,102,58,0.08)",
934
+ border: `1px solid rgba(212,102,58,0.3)`,
935
+ display: "flex",
936
+ flexDirection: "column",
937
+ alignItems: "center",
938
+ gap: 14,
939
+ width: 340,
940
+ }}
941
+ >
942
+ <ClaudeGlyph size={170} glow={0.6} />
943
+ <div style={{ fontFamily: FONT, color: C.ink, fontSize: 34, fontWeight: 600 }}>
944
+ Claude
945
+ </div>
946
+ <div
947
+ style={{
948
+ fontFamily: MONO,
949
+ fontSize: 18,
950
+ color: C.ink2,
951
+ letterSpacing: "0.1em",
952
+ textTransform: "uppercase",
953
+ }}
954
+ >
955
+ reasoning engine
956
+ </div>
957
+ </div>
958
+
959
+ {/* NotebookLM card */}
960
+ <div
961
+ style={{
962
+ transform: `scale(${nP}) translateX(${(1 - nP) * 60}px)`,
963
+ opacity: nP,
964
+ padding: 34,
965
+ borderRadius: 28,
966
+ background: "rgba(79,125,243,0.08)",
967
+ border: `1px solid rgba(79,125,243,0.35)`,
968
+ display: "flex",
969
+ flexDirection: "column",
970
+ alignItems: "center",
971
+ gap: 14,
972
+ width: 340,
973
+ }}
974
+ >
975
+ <NotebookLMGlyph size={170} glow={0.7} />
976
+ <div style={{ fontFamily: FONT, color: C.ink, fontSize: 34, fontWeight: 600 }}>
977
+ NotebookLM
978
+ </div>
979
+ <div
980
+ style={{
981
+ fontFamily: MONO,
982
+ fontSize: 18,
983
+ color: C.ink2,
984
+ letterSpacing: "0.1em",
985
+ textTransform: "uppercase",
986
+ }}
987
+ >
988
+ memory vault
989
+ </div>
990
+ </div>
991
+ </div>
992
+
993
+ {/* Connection wire */}
994
+ <WireConnect
995
+ from={[350, 900]}
996
+ to={[730, 900]}
997
+ progress={wire}
998
+ color={C.gold}
999
+ spark={spark}
1000
+ width={5}
1001
+ />
1002
+
1003
+ {/* Flash at connection moment */}
1004
+ <div
1005
+ style={{
1006
+ position: "absolute",
1007
+ left: 540 - 240,
1008
+ top: 830,
1009
+ width: 480,
1010
+ height: 160,
1011
+ borderRadius: "50%",
1012
+ background: `radial-gradient(circle, rgba(240,194,58,${flash * 0.6}) 0%, transparent 60%)`,
1013
+ filter: "blur(24px)",
1014
+ pointerEvents: "none",
1015
+ }}
1016
+ />
1017
+
1018
+ {/* Bottom connected badge */}
1019
+ <div
1020
+ style={{
1021
+ alignSelf: "center",
1022
+ transform: `scale(${badgeP})`,
1023
+ opacity: badgeP,
1024
+ padding: "16px 28px",
1025
+ borderRadius: 9999,
1026
+ background: "rgba(60,203,127,0.1)",
1027
+ border: `1px solid rgba(60,203,127,0.4)`,
1028
+ display: "flex",
1029
+ alignItems: "center",
1030
+ gap: 12,
1031
+ marginBottom: 60,
1032
+ boxShadow: `0 0 40px rgba(60,203,127,0.3)`,
1033
+ }}
1034
+ >
1035
+ <div
1036
+ style={{
1037
+ width: 12,
1038
+ height: 12,
1039
+ borderRadius: 9999,
1040
+ background: C.success,
1041
+ boxShadow: `0 0 16px ${C.success}`,
1042
+ }}
1043
+ />
1044
+ <div
1045
+ style={{
1046
+ fontFamily: MONO,
1047
+ fontSize: 26,
1048
+ color: C.success,
1049
+ letterSpacing: "0.2em",
1050
+ fontWeight: 600,
1051
+ textTransform: "uppercase",
1052
+ }}
1053
+ >
1054
+ link established
1055
+ </div>
1056
+ </div>
1057
+ </AbsoluteFill>
1058
+ );
1059
+ };
1060
+
1061
+ // ═══════════════════════════════════════════════════════════════
1062
+ // SCENE 5 — SECOND BRAIN
1063
+ // ═══════════════════════════════════════════════════════════════
1064
+
1065
+ const Scene5SecondBrain: React.FC<SP> = ({ frame, fps }) => {
1066
+ const lof = lf(frame, fps, SEGS[4].s);
1067
+
1068
+ const leftP = tween(lof, fps, 0.7, 0, 1, e.backOut, 0.0);
1069
+ const rightP = tween(lof, fps, 0.8, 0, 1, e.backOut, 0.7);
1070
+ const headP = tween(lof, fps, 0.7, 0, 1, e.expoOut, 0.3);
1071
+ const neuralP = tween(lof, fps, 1.0, 0, 1, e.expoOut, 1.3);
1072
+
1073
+ // Neural node positions (inside second brain)
1074
+ const nodes = [
1075
+ [62, 40], [76, 58], [56, 68], [80, 84], [44, 56], [68, 74], [50, 30], [82, 42],
1076
+ ];
1077
+
1078
+ return (
1079
+ <AbsoluteFill style={{ paddingTop: SAFE_TOP, paddingBottom: 1920 - SAFE_BOTTOM }}>
1080
+ <div
1081
+ style={{
1082
+ opacity: headP,
1083
+ textAlign: "center",
1084
+ fontFamily: FONT,
1085
+ color: C.ink,
1086
+ fontSize: 72,
1087
+ fontWeight: 700,
1088
+ letterSpacing: "-0.035em",
1089
+ lineHeight: 1.02,
1090
+ marginTop: 60,
1091
+ }}
1092
+ >
1093
+ A second brain<br />
1094
+ <span style={{ color: C.nlm }}>that never forgets.</span>
1095
+ </div>
1096
+
1097
+ <div
1098
+ style={{
1099
+ flex: 1,
1100
+ display: "flex",
1101
+ alignItems: "center",
1102
+ justifyContent: "center",
1103
+ gap: 40,
1104
+ position: "relative",
1105
+ }}
1106
+ >
1107
+ {/* Left brain = Claude reasoning */}
1108
+ <div style={{ transform: `scale(${leftP})`, opacity: leftP, textAlign: "center" }}>
1109
+ <div style={{ position: "relative", width: 300, height: 300 }}>
1110
+ <svg width="300" height="300" viewBox="0 0 100 100">
1111
+ <defs>
1112
+ <radialGradient id="brainC" cx="50%" cy="50%" r="60%">
1113
+ <stop offset="0" stopColor={C.claude} stopOpacity="0.25" />
1114
+ <stop offset="1" stopColor={C.claude} stopOpacity="0" />
1115
+ </radialGradient>
1116
+ </defs>
1117
+ <circle cx="50" cy="50" r="46" fill="url(#brainC)" />
1118
+ {/* Stylized brain path */}
1119
+ <path
1120
+ d="M30 50 Q30 28 48 28 Q52 20 58 28 Q72 28 72 44 Q78 44 78 54 Q78 70 62 70 Q56 78 48 70 Q30 70 30 54 Z"
1121
+ fill="none"
1122
+ stroke={C.claude}
1123
+ strokeWidth="2"
1124
+ opacity="0.9"
1125
+ />
1126
+ <path
1127
+ d="M50 28 L50 70 M40 40 Q50 46 60 40 M40 60 Q50 54 60 60"
1128
+ fill="none"
1129
+ stroke={C.claude}
1130
+ strokeWidth="1.4"
1131
+ opacity="0.6"
1132
+ />
1133
+ </svg>
1134
+ </div>
1135
+ <div
1136
+ style={{
1137
+ fontFamily: MONO,
1138
+ color: C.ink2,
1139
+ fontSize: 20,
1140
+ letterSpacing: "0.2em",
1141
+ textTransform: "uppercase",
1142
+ marginTop: 8,
1143
+ }}
1144
+ >
1145
+ reasoning
1146
+ </div>
1147
+ </div>
1148
+
1149
+ {/* Synchronize connectors */}
1150
+ <svg width="180" height="300" viewBox="0 0 180 300" style={{ opacity: neuralP }}>
1151
+ <defs>
1152
+ <linearGradient id="syncG" x1="0" y1="0" x2="1" y2="0">
1153
+ <stop offset="0" stopColor={C.claude} />
1154
+ <stop offset="1" stopColor={C.nlm} />
1155
+ </linearGradient>
1156
+ </defs>
1157
+ {[100, 150, 200].map((y, i) => (
1158
+ <line
1159
+ key={i}
1160
+ x1="0"
1161
+ y1={y}
1162
+ x2="180"
1163
+ y2={y}
1164
+ stroke="url(#syncG)"
1165
+ strokeWidth="2"
1166
+ strokeDasharray="6 8"
1167
+ opacity={0.5 + Math.sin(lof * 0.2 + i) * 0.5}
1168
+ />
1169
+ ))}
1170
+ </svg>
1171
+
1172
+ {/* Right brain = NotebookLM memory */}
1173
+ <div style={{ transform: `scale(${rightP})`, opacity: rightP, textAlign: "center" }}>
1174
+ <div style={{ position: "relative", width: 300, height: 300 }}>
1175
+ <svg width="300" height="300" viewBox="0 0 100 100">
1176
+ <defs>
1177
+ <radialGradient id="brainN" cx="50%" cy="50%" r="60%">
1178
+ <stop offset="0" stopColor={C.nlm} stopOpacity="0.25" />
1179
+ <stop offset="1" stopColor={C.nlm} stopOpacity="0" />
1180
+ </radialGradient>
1181
+ </defs>
1182
+ <circle cx="50" cy="50" r="46" fill="url(#brainN)" />
1183
+ <path
1184
+ d="M30 50 Q30 28 48 28 Q52 20 58 28 Q72 28 72 44 Q78 44 78 54 Q78 70 62 70 Q56 78 48 70 Q30 70 30 54 Z"
1185
+ fill="none"
1186
+ stroke={C.nlm}
1187
+ strokeWidth="2"
1188
+ opacity="0.9"
1189
+ />
1190
+ {/* Neural firing nodes */}
1191
+ {nodes.map(([x, y], i) => {
1192
+ const fire = (Math.sin(lof * 0.15 + i) + 1) / 2;
1193
+ return (
1194
+ <circle
1195
+ key={i}
1196
+ cx={x}
1197
+ cy={y}
1198
+ r={1.6}
1199
+ fill={C.nlm}
1200
+ opacity={0.4 + fire * 0.6 * neuralP}
1201
+ />
1202
+ );
1203
+ })}
1204
+ {/* Network lines */}
1205
+ {nodes.slice(0, -1).map(([x1, y1], i) => {
1206
+ const [x2, y2] = nodes[i + 1];
1207
+ return (
1208
+ <line
1209
+ key={i}
1210
+ x1={x1}
1211
+ y1={y1}
1212
+ x2={x2}
1213
+ y2={y2}
1214
+ stroke={C.nlm}
1215
+ strokeWidth="0.5"
1216
+ opacity={0.3 * neuralP}
1217
+ />
1218
+ );
1219
+ })}
1220
+ </svg>
1221
+ </div>
1222
+ <div
1223
+ style={{
1224
+ fontFamily: MONO,
1225
+ color: C.ink2,
1226
+ fontSize: 20,
1227
+ letterSpacing: "0.2em",
1228
+ textTransform: "uppercase",
1229
+ marginTop: 8,
1230
+ }}
1231
+ >
1232
+ memory
1233
+ </div>
1234
+ </div>
1235
+ </div>
1236
+ </AbsoluteFill>
1237
+ );
1238
+ };
1239
+
1240
+ // ═══════════════════════════════════════════════════════════════
1241
+ // SCENE 6 — BENEFITS 1 (Persistent memory, lower costs)
1242
+ // ═══════════════════════════════════════════════════════════════
1243
+
1244
+ const BenefitCard: React.FC<{
1245
+ icon: React.ReactNode;
1246
+ title: string;
1247
+ sub: string;
1248
+ tone: string;
1249
+ reveal: number;
1250
+ }> = ({ icon, title, sub, tone, reveal }) => (
1251
+ <div
1252
+ style={{
1253
+ transform: `translateY(${(1 - reveal) * 60}px)`,
1254
+ opacity: reveal,
1255
+ padding: "28px 32px",
1256
+ borderRadius: 22,
1257
+ background: "rgba(255,255,255,0.03)",
1258
+ border: `1px solid ${C.line}`,
1259
+ display: "flex",
1260
+ alignItems: "center",
1261
+ gap: 24,
1262
+ position: "relative",
1263
+ overflow: "hidden",
1264
+ }}
1265
+ >
1266
+ <div
1267
+ style={{
1268
+ position: "absolute",
1269
+ left: 0,
1270
+ top: 0,
1271
+ bottom: 0,
1272
+ width: 4,
1273
+ background: tone,
1274
+ boxShadow: `0 0 20px ${tone}`,
1275
+ }}
1276
+ />
1277
+ <div
1278
+ style={{
1279
+ width: 80,
1280
+ height: 80,
1281
+ flexShrink: 0,
1282
+ borderRadius: 20,
1283
+ background: "rgba(255,255,255,0.04)",
1284
+ border: `1px solid ${C.line}`,
1285
+ display: "flex",
1286
+ alignItems: "center",
1287
+ justifyContent: "center",
1288
+ color: tone,
1289
+ }}
1290
+ >
1291
+ {icon}
1292
+ </div>
1293
+ <div style={{ flex: 1 }}>
1294
+ <div style={{ fontFamily: FONT, color: C.ink, fontSize: 38, fontWeight: 600, letterSpacing: "-0.02em" }}>
1295
+ {title}
1296
+ </div>
1297
+ <div
1298
+ style={{
1299
+ fontFamily: FONT,
1300
+ color: C.ink2,
1301
+ fontSize: 24,
1302
+ marginTop: 4,
1303
+ }}
1304
+ >
1305
+ {sub}
1306
+ </div>
1307
+ </div>
1308
+ </div>
1309
+ );
1310
+
1311
+ const Scene6Benefits1: React.FC<SP> = ({ frame, fps }) => {
1312
+ const lof = lf(frame, fps, SEGS[5].s);
1313
+
1314
+ const headP = tween(lof, fps, 0.6, 0, 1, e.expoOut, 0);
1315
+ const c1 = tween(lof, fps, 0.8, 0, 1, e.backOut, 0.7);
1316
+ const c2 = tween(lof, fps, 0.8, 0, 1, e.backOut, 1.3);
1317
+
1318
+ // Animated savings count
1319
+ const saveRaw = tween(lof, fps, 1.6, 0, 87, e.expoOut, 1.6);
1320
+
1321
+ return (
1322
+ <AbsoluteFill
1323
+ style={{
1324
+ paddingTop: SAFE_TOP,
1325
+ paddingBottom: 1920 - SAFE_BOTTOM,
1326
+ paddingLeft: 60,
1327
+ paddingRight: 60,
1328
+ }}
1329
+ >
1330
+ <div
1331
+ style={{
1332
+ opacity: headP,
1333
+ fontFamily: FONT,
1334
+ color: C.ink,
1335
+ fontSize: 76,
1336
+ fontWeight: 700,
1337
+ letterSpacing: "-0.035em",
1338
+ lineHeight: 1,
1339
+ marginTop: 40,
1340
+ marginBottom: 48,
1341
+ }}
1342
+ >
1343
+ What you get.
1344
+ </div>
1345
+
1346
+ <div style={{ display: "flex", flexDirection: "column", gap: 24 }}>
1347
+ <BenefitCard
1348
+ reveal={c1}
1349
+ tone={C.success}
1350
+ title="Persistent memory"
1351
+ sub="Every session, recalled. Never starts cold."
1352
+ icon={
1353
+ <svg width="42" height="42" viewBox="0 0 24 24" fill="none">
1354
+ <rect x="3" y="5" width="18" height="14" rx="3" stroke="currentColor" strokeWidth="2" />
1355
+ <path d="M7 10h10M7 14h6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
1356
+ <circle cx="18" cy="17" r="3" fill="currentColor" />
1357
+ </svg>
1358
+ }
1359
+ />
1360
+ <BenefitCard
1361
+ reveal={c2}
1362
+ tone={C.nlm}
1363
+ title={`~${Math.round(saveRaw)}% fewer tokens`}
1364
+ sub="Skip the re-briefs. Context lives next door."
1365
+ icon={
1366
+ <svg width="42" height="42" viewBox="0 0 24 24" fill="none">
1367
+ <path
1368
+ d="M12 3v12m0 0l-4-4m4 4l4-4M5 19h14"
1369
+ stroke="currentColor"
1370
+ strokeWidth="2"
1371
+ strokeLinecap="round"
1372
+ strokeLinejoin="round"
1373
+ />
1374
+ </svg>
1375
+ }
1376
+ />
1377
+ </div>
1378
+
1379
+ {/* Receipts */}
1380
+ <div
1381
+ style={{
1382
+ marginTop: 40,
1383
+ opacity: c2 * 0.9,
1384
+ fontFamily: MONO,
1385
+ color: C.ink2,
1386
+ fontSize: 22,
1387
+ letterSpacing: "0.1em",
1388
+ textTransform: "uppercase",
1389
+ textAlign: "center",
1390
+ }}
1391
+ >
1392
+ · same brain · lower bill ·
1393
+ </div>
1394
+ </AbsoluteFill>
1395
+ );
1396
+ };
1397
+
1398
+ // ═══════════════════════════════════════════════════════════════
1399
+ // SCENE 7 — BENEFITS 2 (Infographics, videos, research)
1400
+ // ═══════════════════════════════════════════════════════════════
1401
+
1402
+ const Scene7Benefits2: React.FC<SP> = ({ frame, fps }) => {
1403
+ const lof = lf(frame, fps, SEGS[6].s);
1404
+
1405
+ const headP = tween(lof, fps, 0.6, 0, 1, e.expoOut, 0);
1406
+ const tiles = [
1407
+ {
1408
+ title: "Infographics",
1409
+ sub: "auto-designed",
1410
+ tone: C.gold,
1411
+ delay: 0.6,
1412
+ icon: (
1413
+ <svg width="54" height="54" viewBox="0 0 24 24" fill="none">
1414
+ <rect x="3" y="12" width="4" height="9" rx="1" fill="currentColor" />
1415
+ <rect x="10" y="7" width="4" height="14" rx="1" fill="currentColor" />
1416
+ <rect x="17" y="3" width="4" height="18" rx="1" fill="currentColor" />
1417
+ </svg>
1418
+ ),
1419
+ },
1420
+ {
1421
+ title: "Cinematic video",
1422
+ sub: "storyboard → clip",
1423
+ tone: C.claude,
1424
+ delay: 1.0,
1425
+ icon: (
1426
+ <svg width="54" height="54" viewBox="0 0 24 24" fill="none">
1427
+ <rect x="3" y="5" width="18" height="14" rx="3" stroke="currentColor" strokeWidth="2" />
1428
+ <path d="M10 9.5v5l5-2.5z" fill="currentColor" />
1429
+ </svg>
1430
+ ),
1431
+ },
1432
+ {
1433
+ title: "Deep research",
1434
+ sub: "sourced & cited",
1435
+ tone: C.nlm,
1436
+ delay: 1.4,
1437
+ icon: (
1438
+ <svg width="54" height="54" viewBox="0 0 24 24" fill="none">
1439
+ <circle cx="11" cy="11" r="6" stroke="currentColor" strokeWidth="2" />
1440
+ <path d="M16 16l5 5" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
1441
+ <path d="M8 11h6M11 8v6" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
1442
+ </svg>
1443
+ ),
1444
+ },
1445
+ ];
1446
+
1447
+ return (
1448
+ <AbsoluteFill
1449
+ style={{
1450
+ paddingTop: SAFE_TOP,
1451
+ paddingBottom: 1920 - SAFE_BOTTOM,
1452
+ paddingLeft: 60,
1453
+ paddingRight: 60,
1454
+ }}
1455
+ >
1456
+ <div
1457
+ style={{
1458
+ opacity: headP,
1459
+ fontFamily: FONT,
1460
+ color: C.ink,
1461
+ fontSize: 72,
1462
+ fontWeight: 700,
1463
+ letterSpacing: "-0.035em",
1464
+ lineHeight: 1,
1465
+ marginTop: 40,
1466
+ marginBottom: 48,
1467
+ }}
1468
+ >
1469
+ Plus — all free,
1470
+ <br />
1471
+ <span style={{ color: C.nlm }}>in NotebookLM.</span>
1472
+ </div>
1473
+
1474
+ <div style={{ display: "flex", flexDirection: "column", gap: 22 }}>
1475
+ {tiles.map((t, i) => {
1476
+ const reveal = tween(lof, fps, 0.7, 0, 1, e.backOut, t.delay);
1477
+ const side = i % 2 === 0 ? -1 : 1;
1478
+ return (
1479
+ <div
1480
+ key={i}
1481
+ style={{
1482
+ transform: `translateX(${(1 - reveal) * side * 80}px)`,
1483
+ opacity: reveal,
1484
+ padding: "26px 30px",
1485
+ borderRadius: 22,
1486
+ background: "rgba(255,255,255,0.03)",
1487
+ border: `1px solid ${C.line}`,
1488
+ display: "flex",
1489
+ alignItems: "center",
1490
+ gap: 24,
1491
+ }}
1492
+ >
1493
+ <div
1494
+ style={{
1495
+ width: 96,
1496
+ height: 96,
1497
+ flexShrink: 0,
1498
+ borderRadius: 22,
1499
+ background: `linear-gradient(135deg, ${t.tone}22, transparent)`,
1500
+ border: `1px solid ${t.tone}55`,
1501
+ display: "flex",
1502
+ alignItems: "center",
1503
+ justifyContent: "center",
1504
+ color: t.tone,
1505
+ boxShadow: `0 0 24px ${t.tone}33`,
1506
+ }}
1507
+ >
1508
+ {t.icon}
1509
+ </div>
1510
+ <div style={{ flex: 1 }}>
1511
+ <div
1512
+ style={{
1513
+ fontFamily: FONT,
1514
+ color: C.ink,
1515
+ fontSize: 42,
1516
+ fontWeight: 600,
1517
+ letterSpacing: "-0.02em",
1518
+ }}
1519
+ >
1520
+ {t.title}
1521
+ </div>
1522
+ <div
1523
+ style={{
1524
+ fontFamily: MONO,
1525
+ color: C.ink2,
1526
+ fontSize: 22,
1527
+ letterSpacing: "0.1em",
1528
+ textTransform: "uppercase",
1529
+ marginTop: 4,
1530
+ }}
1531
+ >
1532
+ {t.sub}
1533
+ </div>
1534
+ </div>
1535
+ <div
1536
+ style={{
1537
+ fontFamily: MONO,
1538
+ color: t.tone,
1539
+ fontSize: 24,
1540
+ fontWeight: 600,
1541
+ opacity: reveal,
1542
+ }}
1543
+ >
1544
+ FREE
1545
+ </div>
1546
+ </div>
1547
+ );
1548
+ })}
1549
+ </div>
1550
+ </AbsoluteFill>
1551
+ );
1552
+ };
1553
+
1554
+ // ═══════════════════════════════════════════════════════════════
1555
+ // SCENE 8 — WRAPUP SKILL (convo flows into notebook)
1556
+ // ═══════════════════════════════════════════════════════════════
1557
+
1558
+ const Scene8Wrapup: React.FC<SP> = ({ frame, fps }) => {
1559
+ const lof = lf(frame, fps, SEGS[7].s);
1560
+
1561
+ const headP = tween(lof, fps, 0.6, 0, 1, e.expoOut, 0);
1562
+ const badgeP = tween(lof, fps, 0.7, 0, 1, e.backOut, 0.4);
1563
+
1564
+ // Message bubbles condense into NotebookLM
1565
+ const bubbleCount = 4;
1566
+ const bubbleDelays = [1.0, 1.4, 1.8, 2.2];
1567
+ const nbP = tween(lof, fps, 0.8, 0, 1, e.backOut, 1.8);
1568
+ const saveP = tween(lof, fps, 0.7, 0, 1, e.expoOut, 4.6);
1569
+
1570
+ return (
1571
+ <AbsoluteFill style={{ paddingTop: SAFE_TOP, paddingBottom: 1920 - SAFE_BOTTOM }}>
1572
+ {/* Header */}
1573
+ <div
1574
+ style={{
1575
+ opacity: headP,
1576
+ textAlign: "center",
1577
+ marginTop: 40,
1578
+ }}
1579
+ >
1580
+ <div
1581
+ style={{
1582
+ fontFamily: MONO,
1583
+ color: C.ink2,
1584
+ fontSize: 22,
1585
+ letterSpacing: "0.25em",
1586
+ textTransform: "uppercase",
1587
+ }}
1588
+ >
1589
+ the secret
1590
+ </div>
1591
+ <div
1592
+ style={{
1593
+ fontFamily: FONT,
1594
+ color: C.ink,
1595
+ fontSize: 76,
1596
+ fontWeight: 700,
1597
+ letterSpacing: "-0.035em",
1598
+ lineHeight: 1,
1599
+ marginTop: 18,
1600
+ }}
1601
+ >
1602
+ "wrap-up" skill.
1603
+ </div>
1604
+ </div>
1605
+
1606
+ {/* Badge */}
1607
+ <div
1608
+ style={{
1609
+ alignSelf: "center",
1610
+ transform: `scale(${badgeP})`,
1611
+ opacity: badgeP,
1612
+ marginTop: 22,
1613
+ padding: "10px 22px",
1614
+ borderRadius: 9999,
1615
+ background: `linear-gradient(90deg, ${C.claude}22, ${C.nlm}22)`,
1616
+ border: `1px solid ${C.line}`,
1617
+ fontFamily: MONO,
1618
+ color: C.ink,
1619
+ fontSize: 22,
1620
+ letterSpacing: "0.1em",
1621
+ fontWeight: 600,
1622
+ }}
1623
+ >
1624
+ runs after every session
1625
+ </div>
1626
+
1627
+ {/* Flow diagram */}
1628
+ <div
1629
+ style={{
1630
+ flex: 1,
1631
+ display: "flex",
1632
+ alignItems: "center",
1633
+ justifyContent: "center",
1634
+ position: "relative",
1635
+ padding: "40px 60px",
1636
+ marginTop: 20,
1637
+ }}
1638
+ >
1639
+ {/* Left: Chat session with bubbles collapsing */}
1640
+ <div style={{ flex: 1, position: "relative", height: 440 }}>
1641
+ <div
1642
+ style={{
1643
+ fontFamily: MONO,
1644
+ color: C.ink2,
1645
+ fontSize: 18,
1646
+ letterSpacing: "0.2em",
1647
+ textTransform: "uppercase",
1648
+ marginBottom: 14,
1649
+ }}
1650
+ >
1651
+ session
1652
+ </div>
1653
+ {Array.from({ length: bubbleCount }).map((_, i) => {
1654
+ const appear = tween(lof, fps, 0.4, 0, 1, e.expoOut, bubbleDelays[i]);
1655
+ const shrink = tween(lof, fps, 0.9, 1, 0, e.expoInOut, bubbleDelays[i] + 2.0);
1656
+ const yMove = (1 - shrink) * (400 + i * 20);
1657
+ const xMove = (1 - shrink) * 380;
1658
+ return (
1659
+ <div
1660
+ key={i}
1661
+ style={{
1662
+ position: "absolute",
1663
+ top: i * 78,
1664
+ left: i % 2 === 0 ? 0 : 40,
1665
+ opacity: appear * (shrink > 0.05 ? 1 : 0),
1666
+ transform: `translate(${xMove}px, ${yMove}px) scale(${Math.max(0.2, shrink)})`,
1667
+ padding: "14px 20px",
1668
+ borderRadius: 18,
1669
+ background: i % 2 === 0 ? "rgba(212,102,58,0.15)" : "rgba(79,125,243,0.15)",
1670
+ border: `1px solid ${i % 2 === 0 ? "rgba(212,102,58,0.4)" : "rgba(79,125,243,0.4)"}`,
1671
+ maxWidth: 340,
1672
+ fontFamily: FONT,
1673
+ color: C.ink,
1674
+ fontSize: 22,
1675
+ lineHeight: 1.35,
1676
+ }}
1677
+ >
1678
+ {
1679
+ [
1680
+ "Here's the API pattern…",
1681
+ "Use async iterators.",
1682
+ "Cache invalidation by key?",
1683
+ "Use SWR + stale-while-revalidate.",
1684
+ ][i]
1685
+ }
1686
+ </div>
1687
+ );
1688
+ })}
1689
+ </div>
1690
+
1691
+ {/* Arrow */}
1692
+ <svg width="140" height="60" viewBox="0 0 140 60" style={{ flexShrink: 0 }}>
1693
+ <defs>
1694
+ <linearGradient id="arrG" x1="0" y1="0" x2="1" y2="0">
1695
+ <stop offset="0" stopColor={C.claude} />
1696
+ <stop offset="1" stopColor={C.nlm} />
1697
+ </linearGradient>
1698
+ </defs>
1699
+ <path
1700
+ d="M 10 30 L 120 30"
1701
+ stroke="url(#arrG)"
1702
+ strokeWidth="4"
1703
+ strokeLinecap="round"
1704
+ strokeDasharray="400"
1705
+ strokeDashoffset={(1 - nbP) * 400}
1706
+ />
1707
+ <path
1708
+ d="M 110 20 L 130 30 L 110 40"
1709
+ fill="none"
1710
+ stroke={C.nlm}
1711
+ strokeWidth="4"
1712
+ strokeLinecap="round"
1713
+ strokeLinejoin="round"
1714
+ opacity={nbP}
1715
+ />
1716
+ </svg>
1717
+
1718
+ {/* Right: NotebookLM destination */}
1719
+ <div
1720
+ style={{
1721
+ flex: 1,
1722
+ display: "flex",
1723
+ flexDirection: "column",
1724
+ alignItems: "center",
1725
+ gap: 16,
1726
+ transform: `scale(${nbP})`,
1727
+ opacity: nbP,
1728
+ }}
1729
+ >
1730
+ <NotebookLMGlyph size={220} glow={0.75} />
1731
+ <div
1732
+ style={{
1733
+ fontFamily: FONT,
1734
+ color: C.ink,
1735
+ fontSize: 34,
1736
+ fontWeight: 600,
1737
+ letterSpacing: "-0.02em",
1738
+ }}
1739
+ >
1740
+ NotebookLM
1741
+ </div>
1742
+ <div
1743
+ style={{
1744
+ transform: `scale(${saveP})`,
1745
+ opacity: saveP,
1746
+ padding: "8px 18px",
1747
+ borderRadius: 9999,
1748
+ background: "rgba(60,203,127,0.12)",
1749
+ border: `1px solid rgba(60,203,127,0.4)`,
1750
+ fontFamily: MONO,
1751
+ color: C.success,
1752
+ fontSize: 20,
1753
+ letterSpacing: "0.15em",
1754
+ textTransform: "uppercase",
1755
+ fontWeight: 600,
1756
+ }}
1757
+ >
1758
+ + saved
1759
+ </div>
1760
+ </div>
1761
+ </div>
1762
+ </AbsoluteFill>
1763
+ );
1764
+ };
1765
+
1766
+ // ═══════════════════════════════════════════════════════════════
1767
+ // SCENE 9 — SEMANTIC SEARCH (pulls only what it needs)
1768
+ // ═══════════════════════════════════════════════════════════════
1769
+
1770
+ const Scene9Semantic: React.FC<SP> = ({ frame, fps }) => {
1771
+ const lof = lf(frame, fps, SEGS[8].s);
1772
+
1773
+ const headP = tween(lof, fps, 0.6, 0, 1, e.expoOut, 0);
1774
+ const searchP = tween(lof, fps, 0.7, 0, 1, e.backOut, 0.6);
1775
+
1776
+ // Typewriter for query
1777
+ const query = "how did we structure that auth flow?";
1778
+ const typeP = tween(lof, fps, 1.6, 0, query.length, e.circOut, 1.1);
1779
+ const shown = query.slice(0, Math.max(0, Math.round(typeP)));
1780
+ const cursorOn = Math.floor(lof / 10) % 2 === 0;
1781
+
1782
+ // Vector grid reveal
1783
+ const gridP = tween(lof, fps, 0.9, 0, 1, e.expoOut, 2.0);
1784
+
1785
+ // Highlight 4 specific cells
1786
+ const hitIdx = [11, 23, 35, 52];
1787
+ const hitP = tween(lof, fps, 1.0, 0, 1, e.expoOut, 3.0);
1788
+
1789
+ // Summary pill
1790
+ const summaryP = tween(lof, fps, 0.7, 0, 1, e.backOut, 4.2);
1791
+
1792
+ return (
1793
+ <AbsoluteFill
1794
+ style={{
1795
+ paddingTop: SAFE_TOP,
1796
+ paddingBottom: 1920 - SAFE_BOTTOM,
1797
+ paddingLeft: 60,
1798
+ paddingRight: 60,
1799
+ }}
1800
+ >
1801
+ <div
1802
+ style={{
1803
+ opacity: headP,
1804
+ fontFamily: FONT,
1805
+ color: C.ink,
1806
+ fontSize: 72,
1807
+ fontWeight: 700,
1808
+ letterSpacing: "-0.035em",
1809
+ lineHeight: 1,
1810
+ marginTop: 40,
1811
+ marginBottom: 40,
1812
+ }}
1813
+ >
1814
+ Claude pulls only
1815
+ <br />
1816
+ <span style={{ color: C.gold }}>what it needs.</span>
1817
+ </div>
1818
+
1819
+ {/* Search bar */}
1820
+ <div
1821
+ style={{
1822
+ transform: `translateY(${(1 - searchP) * 30}px) scale(${0.96 + searchP * 0.04})`,
1823
+ opacity: searchP,
1824
+ padding: "22px 24px",
1825
+ borderRadius: 18,
1826
+ background: "rgba(255,255,255,0.04)",
1827
+ border: `1px solid ${C.lineStrong}`,
1828
+ display: "flex",
1829
+ alignItems: "center",
1830
+ gap: 18,
1831
+ boxShadow: `0 0 40px rgba(240,194,58,0.12)`,
1832
+ marginBottom: 36,
1833
+ }}
1834
+ >
1835
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none" style={{ color: C.gold }}>
1836
+ <circle cx="11" cy="11" r="6" stroke="currentColor" strokeWidth="2" />
1837
+ <path d="M16 16l5 5" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
1838
+ </svg>
1839
+ <div
1840
+ style={{
1841
+ fontFamily: MONO,
1842
+ color: C.ink,
1843
+ fontSize: 28,
1844
+ letterSpacing: "0.01em",
1845
+ flex: 1,
1846
+ }}
1847
+ >
1848
+ {shown}
1849
+ <span
1850
+ style={{
1851
+ display: "inline-block",
1852
+ width: 2,
1853
+ height: 28,
1854
+ background: C.gold,
1855
+ marginLeft: 2,
1856
+ verticalAlign: "middle",
1857
+ opacity: cursorOn ? 1 : 0,
1858
+ }}
1859
+ />
1860
+ </div>
1861
+ </div>
1862
+
1863
+ {/* Vector grid representing memory */}
1864
+ <div
1865
+ style={{
1866
+ flex: 1,
1867
+ padding: 20,
1868
+ borderRadius: 20,
1869
+ background: "rgba(79,125,243,0.04)",
1870
+ border: `1px solid ${C.line}`,
1871
+ opacity: gridP,
1872
+ }}
1873
+ >
1874
+ <div
1875
+ style={{
1876
+ fontFamily: MONO,
1877
+ color: C.ink2,
1878
+ fontSize: 18,
1879
+ letterSpacing: "0.2em",
1880
+ textTransform: "uppercase",
1881
+ marginBottom: 14,
1882
+ display: "flex",
1883
+ justifyContent: "space-between",
1884
+ }}
1885
+ >
1886
+ <span>memory vectors · 12,847</span>
1887
+ <span style={{ color: hitP > 0.3 ? C.success : C.ink2 }}>
1888
+ {hitP > 0.3 ? `matches · ${hitIdx.length}` : "searching…"}
1889
+ </span>
1890
+ </div>
1891
+ <div
1892
+ style={{
1893
+ display: "grid",
1894
+ gridTemplateColumns: "repeat(10, 1fr)",
1895
+ gap: 8,
1896
+ }}
1897
+ >
1898
+ {Array.from({ length: 70 }).map((_, i) => {
1899
+ const hit = hitIdx.includes(i);
1900
+ const cellP = interpolate(
1901
+ gridP,
1902
+ [0, 1],
1903
+ [0, 1],
1904
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" },
1905
+ );
1906
+ const hitFade = hit ? hitP : 0;
1907
+ return (
1908
+ <div
1909
+ key={i}
1910
+ style={{
1911
+ aspectRatio: "1/1",
1912
+ borderRadius: 8,
1913
+ background: hit
1914
+ ? `linear-gradient(135deg, ${C.nlm}, ${C.gold})`
1915
+ : `rgba(255,255,255,${0.04 + (i % 7) * 0.008})`,
1916
+ border: hit
1917
+ ? `1px solid ${C.gold}`
1918
+ : `1px solid rgba(255,255,255,0.05)`,
1919
+ transform: `scale(${cellP}) ${hit ? `scale(${1 + hitFade * 0.12})` : ""}`,
1920
+ boxShadow: hit ? `0 0 18px ${C.nlm}` : "none",
1921
+ opacity: cellP,
1922
+ }}
1923
+ />
1924
+ );
1925
+ })}
1926
+ </div>
1927
+ </div>
1928
+
1929
+ {/* Summary */}
1930
+ <div
1931
+ style={{
1932
+ alignSelf: "center",
1933
+ transform: `scale(${summaryP})`,
1934
+ opacity: summaryP,
1935
+ marginTop: 28,
1936
+ padding: "14px 24px",
1937
+ borderRadius: 9999,
1938
+ border: `1px solid rgba(60,203,127,0.4)`,
1939
+ background: "rgba(60,203,127,0.08)",
1940
+ fontFamily: MONO,
1941
+ color: C.success,
1942
+ fontSize: 22,
1943
+ letterSpacing: "0.1em",
1944
+ fontWeight: 600,
1945
+ display: "flex",
1946
+ alignItems: "center",
1947
+ gap: 12,
1948
+ }}
1949
+ >
1950
+ <div style={{ width: 8, height: 8, borderRadius: 9999, background: C.success }} />
1951
+ NO CONTEXT OVERLOAD
1952
+ </div>
1953
+ </AbsoluteFill>
1954
+ );
1955
+ };
1956
+
1957
+ // ═══════════════════════════════════════════════════════════════
1958
+ // SCENE 10 — CHANGES EVERYTHING
1959
+ // ═══════════════════════════════════════════════════════════════
1960
+
1961
+ const Scene10Changes: React.FC<SP> = ({ frame, fps }) => {
1962
+ const lof = lf(frame, fps, SEGS[9].s);
1963
+
1964
+ const p1 = tween(lof, fps, 0.7, 0, 1, e.expoOut, 0);
1965
+ const p2 = tween(lof, fps, 1.0, 0, 1, e.backOut, 0.5);
1966
+ const underline = tween(lof, fps, 0.8, 0, 1, e.expoInOut, 1.3);
1967
+ const flash = interpolate(lof, [0.5 * fps, 0.7 * fps, 1.0 * fps], [0, 1, 0], {
1968
+ extrapolateLeft: "clamp",
1969
+ extrapolateRight: "clamp",
1970
+ });
1971
+
1972
+ return (
1973
+ <AbsoluteFill
1974
+ style={{
1975
+ justifyContent: "center",
1976
+ alignItems: "center",
1977
+ paddingTop: SAFE_TOP,
1978
+ paddingBottom: 1920 - SAFE_BOTTOM,
1979
+ }}
1980
+ >
1981
+ {/* Flash background */}
1982
+ <div
1983
+ style={{
1984
+ position: "absolute",
1985
+ inset: 0,
1986
+ background: `radial-gradient(circle at 50% 50%, rgba(240,194,58,${flash * 0.25}) 0%, transparent 60%)`,
1987
+ pointerEvents: "none",
1988
+ }}
1989
+ />
1990
+
1991
+ <div
1992
+ style={{
1993
+ fontFamily: FONT,
1994
+ color: C.ink,
1995
+ fontSize: 56,
1996
+ fontWeight: 500,
1997
+ letterSpacing: "-0.02em",
1998
+ textAlign: "center",
1999
+ opacity: p1,
2000
+ marginBottom: 16,
2001
+ }}
2002
+ >
2003
+ One setup.
2004
+ </div>
2005
+ <div
2006
+ style={{
2007
+ transform: `scale(${p2})`,
2008
+ opacity: p2,
2009
+ fontFamily: FONT,
2010
+ color: C.ink,
2011
+ fontSize: 150,
2012
+ fontWeight: 700,
2013
+ letterSpacing: "-0.05em",
2014
+ lineHeight: 0.92,
2015
+ textAlign: "center",
2016
+ position: "relative",
2017
+ }}
2018
+ >
2019
+ Changes
2020
+ <br />
2021
+ <span
2022
+ style={{
2023
+ background: `linear-gradient(90deg, ${C.claude} 0%, ${C.gold} 50%, ${C.nlm} 100%)`,
2024
+ WebkitBackgroundClip: "text",
2025
+ backgroundClip: "text",
2026
+ WebkitTextFillColor: "transparent",
2027
+ position: "relative",
2028
+ display: "inline-block",
2029
+ }}
2030
+ >
2031
+ everything.
2032
+ <div
2033
+ style={{
2034
+ position: "absolute",
2035
+ left: 0,
2036
+ bottom: -16,
2037
+ height: 8,
2038
+ width: `${underline * 100}%`,
2039
+ background: `linear-gradient(90deg, ${C.claude}, ${C.gold}, ${C.nlm})`,
2040
+ borderRadius: 4,
2041
+ boxShadow: `0 0 20px ${C.gold}`,
2042
+ }}
2043
+ />
2044
+ </span>
2045
+ </div>
2046
+ </AbsoluteFill>
2047
+ );
2048
+ };
2049
+
2050
+ // ═══════════════════════════════════════════════════════════════
2051
+ // SCENE 11 — CTA (Like + Follow + Comment "AI")
2052
+ // ═══════════════════════════════════════════════════════════════
2053
+
2054
+ const Scene11CTA: React.FC<SP> = ({ frame, fps }) => {
2055
+ const lof = lf(frame, fps, SEGS[10].s);
2056
+
2057
+ const headP = tween(lof, fps, 0.6, 0, 1, e.expoOut, 0);
2058
+ const likeP = tween(lof, fps, 0.7, 0, 1, e.backOut, 0.3);
2059
+ const followP = tween(lof, fps, 0.7, 0, 1, e.backOut, 0.7);
2060
+ const commentP = tween(lof, fps, 0.7, 0, 1, e.backOut, 1.1);
2061
+
2062
+ // Type "AI" into comment box
2063
+ const typeStart = 2.0;
2064
+ const aiText = "AI";
2065
+ const typeP = tween(lof, fps, 0.5, 0, aiText.length, e.circOut, typeStart);
2066
+ const typed = aiText.slice(0, Math.round(typeP));
2067
+ const cursorOn = Math.floor(lof / 8) % 2 === 0;
2068
+
2069
+ // Heart pulse
2070
+ const heartPulse = 1 + Math.sin(lof * 0.3) * 0.05 * likeP;
2071
+
2072
+ return (
2073
+ <AbsoluteFill
2074
+ style={{
2075
+ paddingTop: SAFE_TOP,
2076
+ paddingBottom: 1920 - SAFE_BOTTOM,
2077
+ paddingLeft: 60,
2078
+ paddingRight: 60,
2079
+ }}
2080
+ >
2081
+ <div
2082
+ style={{
2083
+ opacity: headP,
2084
+ textAlign: "center",
2085
+ fontFamily: FONT,
2086
+ color: C.ink,
2087
+ fontSize: 68,
2088
+ fontWeight: 700,
2089
+ letterSpacing: "-0.035em",
2090
+ lineHeight: 1,
2091
+ marginTop: 40,
2092
+ }}
2093
+ >
2094
+ Want the exact steps?
2095
+ </div>
2096
+ <div
2097
+ style={{
2098
+ opacity: headP,
2099
+ textAlign: "center",
2100
+ fontFamily: MONO,
2101
+ color: C.ink2,
2102
+ fontSize: 22,
2103
+ letterSpacing: "0.2em",
2104
+ textTransform: "uppercase",
2105
+ marginTop: 16,
2106
+ }}
2107
+ >
2108
+ I'll send them over.
2109
+ </div>
2110
+
2111
+ {/* Engagement row */}
2112
+ <div
2113
+ style={{
2114
+ flex: 1,
2115
+ display: "flex",
2116
+ flexDirection: "column",
2117
+ gap: 22,
2118
+ justifyContent: "center",
2119
+ }}
2120
+ >
2121
+ {/* Like button */}
2122
+ <div
2123
+ style={{
2124
+ transform: `translateX(${(1 - likeP) * -60}px) scale(${heartPulse})`,
2125
+ opacity: likeP,
2126
+ padding: "20px 26px",
2127
+ borderRadius: 22,
2128
+ background: "rgba(255,255,255,0.03)",
2129
+ border: `1px solid ${C.line}`,
2130
+ display: "flex",
2131
+ alignItems: "center",
2132
+ gap: 22,
2133
+ }}
2134
+ >
2135
+ <svg width="60" height="60" viewBox="0 0 24 24" fill={C.danger}>
2136
+ <path d="M12 21s-7-4.5-9.5-9A5.5 5.5 0 0 1 12 6a5.5 5.5 0 0 1 9.5 6C19 16.5 12 21 12 21z" />
2137
+ </svg>
2138
+ <div style={{ flex: 1 }}>
2139
+ <div
2140
+ style={{
2141
+ fontFamily: FONT,
2142
+ color: C.ink,
2143
+ fontSize: 38,
2144
+ fontWeight: 600,
2145
+ letterSpacing: "-0.02em",
2146
+ }}
2147
+ >
2148
+ Like this
2149
+ </div>
2150
+ <div style={{ fontFamily: MONO, color: C.ink2, fontSize: 20, letterSpacing: "0.1em" }}>
2151
+ so more people see it
2152
+ </div>
2153
+ </div>
2154
+ </div>
2155
+
2156
+ {/* Comment box with typing */}
2157
+ <div
2158
+ style={{
2159
+ transform: `translateX(${(1 - commentP) * -60}px)`,
2160
+ opacity: commentP,
2161
+ padding: "20px 26px",
2162
+ borderRadius: 22,
2163
+ background: `linear-gradient(135deg, rgba(212,102,58,0.15), rgba(240,194,58,0.1))`,
2164
+ border: `1px solid ${C.gold}`,
2165
+ boxShadow: `0 0 40px rgba(240,194,58,0.25)`,
2166
+ display: "flex",
2167
+ alignItems: "center",
2168
+ gap: 22,
2169
+ }}
2170
+ >
2171
+ <svg width="60" height="60" viewBox="0 0 24 24" fill="none">
2172
+ <path
2173
+ d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"
2174
+ stroke={C.gold}
2175
+ strokeWidth="2.5"
2176
+ strokeLinejoin="round"
2177
+ />
2178
+ </svg>
2179
+ <div style={{ flex: 1 }}>
2180
+ <div
2181
+ style={{
2182
+ fontFamily: MONO,
2183
+ color: C.ink2,
2184
+ fontSize: 20,
2185
+ letterSpacing: "0.1em",
2186
+ textTransform: "uppercase",
2187
+ marginBottom: 4,
2188
+ }}
2189
+ >
2190
+ comment
2191
+ </div>
2192
+ <div
2193
+ style={{
2194
+ fontFamily: FONT,
2195
+ color: C.ink,
2196
+ fontSize: 52,
2197
+ fontWeight: 700,
2198
+ letterSpacing: "0.08em",
2199
+ }}
2200
+ >
2201
+ "{typed}
2202
+ <span
2203
+ style={{
2204
+ display: "inline-block",
2205
+ width: 3,
2206
+ height: 48,
2207
+ background: C.gold,
2208
+ marginLeft: 4,
2209
+ verticalAlign: "middle",
2210
+ opacity: cursorOn ? 1 : 0,
2211
+ }}
2212
+ />
2213
+ "
2214
+ </div>
2215
+ </div>
2216
+ </div>
2217
+ </div>
2218
+
2219
+ {/* Bottom line */}
2220
+ <div
2221
+ style={{
2222
+ opacity: commentP,
2223
+ textAlign: "center",
2224
+ fontFamily: MONO,
2225
+ color: C.ink2,
2226
+ fontSize: 22,
2227
+ letterSpacing: "0.15em",
2228
+ textTransform: "uppercase",
2229
+ marginBottom: 40,
2230
+ }}
2231
+ >
2232
+ I'll DM the full setup →
2233
+ </div>
2234
+ </AbsoluteFill>
2235
+ );
2236
+ };
2237
+
2238
+ // ═══════════════════════════════════════════════════════════════
2239
+ // SCENE ROUTER — renders the right scene for each SEGS window
2240
+ // ═══════════════════════════════════════════════════════════════
2241
+
2242
+ const SceneRouter: React.FC = () => {
2243
+ const frame = useCurrentFrame();
2244
+ const { fps } = useVideoConfig();
2245
+
2246
+ // Pick accent tone for background based on currently dominant scene
2247
+ const sceneIdx = SEGS.findIndex((seg) => frame < seg.e * fps);
2248
+ const accentByScene = [
2249
+ C.claudeGlow, // 1 hook
2250
+ C.claudeGlow, // 2 gift
2251
+ "rgba(232,72,58,0.4)", // 3 problem
2252
+ C.nlmGlow, // 4 connect
2253
+ C.nlmGlow, // 5 second brain
2254
+ "rgba(60,203,127,0.38)", // 6 benefits1
2255
+ "rgba(240,194,58,0.38)", // 7 benefits2
2256
+ C.nlmGlow, // 8 wrapup
2257
+ "rgba(240,194,58,0.4)", // 9 semantic
2258
+ "rgba(240,194,58,0.45)", // 10 changes
2259
+ C.claudeGlow, // 11 cta
2260
+ ];
2261
+ const accent = accentByScene[sceneIdx === -1 ? accentByScene.length - 1 : sceneIdx];
2262
+
2263
+ const sp = { frame, fps };
2264
+
2265
+ return (
2266
+ <>
2267
+ <Background frame={frame} accent={accent} />
2268
+ <AbsoluteFill style={{ opacity: so(frame, fps, SEGS[0].s, SEGS[0].e) }}>
2269
+ <Scene1Hook {...sp} />
2270
+ </AbsoluteFill>
2271
+ <AbsoluteFill style={{ opacity: so(frame, fps, SEGS[1].s, SEGS[1].e) }}>
2272
+ <Scene2Gift {...sp} />
2273
+ </AbsoluteFill>
2274
+ <AbsoluteFill style={{ opacity: so(frame, fps, SEGS[2].s, SEGS[2].e) }}>
2275
+ <Scene3Problem {...sp} />
2276
+ </AbsoluteFill>
2277
+ <AbsoluteFill style={{ opacity: so(frame, fps, SEGS[3].s, SEGS[3].e) }}>
2278
+ <Scene4Connect {...sp} />
2279
+ </AbsoluteFill>
2280
+ <AbsoluteFill style={{ opacity: so(frame, fps, SEGS[4].s, SEGS[4].e) }}>
2281
+ <Scene5SecondBrain {...sp} />
2282
+ </AbsoluteFill>
2283
+ <AbsoluteFill style={{ opacity: so(frame, fps, SEGS[5].s, SEGS[5].e) }}>
2284
+ <Scene6Benefits1 {...sp} />
2285
+ </AbsoluteFill>
2286
+ <AbsoluteFill style={{ opacity: so(frame, fps, SEGS[6].s, SEGS[6].e) }}>
2287
+ <Scene7Benefits2 {...sp} />
2288
+ </AbsoluteFill>
2289
+ <AbsoluteFill style={{ opacity: so(frame, fps, SEGS[7].s, SEGS[7].e) }}>
2290
+ <Scene8Wrapup {...sp} />
2291
+ </AbsoluteFill>
2292
+ <AbsoluteFill style={{ opacity: so(frame, fps, SEGS[8].s, SEGS[8].e) }}>
2293
+ <Scene9Semantic {...sp} />
2294
+ </AbsoluteFill>
2295
+ <AbsoluteFill style={{ opacity: so(frame, fps, SEGS[9].s, SEGS[9].e) }}>
2296
+ <Scene10Changes {...sp} />
2297
+ </AbsoluteFill>
2298
+ <AbsoluteFill style={{ opacity: so(frame, fps, SEGS[10].s, SEGS[10].e) }}>
2299
+ <Scene11CTA {...sp} />
2300
+ </AbsoluteFill>
2301
+ </>
2302
+ );
2303
+ };
2304
+
2305
+ // ═══════════════════════════════════════════════════════════════
2306
+ // MAIN COMPOSITION
2307
+ // ═══════════════════════════════════════════════════════════════
2308
+
2309
+ export const NotebookLMReel: React.FC = () => {
2310
+ return (
2311
+ <AbsoluteFill style={{ background: C.bg }}>
2312
+ {/* REFERENCE-STRIP: <Audio> tag removed — bring your own voiceover */}
2313
+ <SceneRouter />
2314
+ </AbsoluteFill>
2315
+ );
2316
+ };