@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,3040 @@
1
+ /**
2
+ * REFERENCE — StitchReel (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/StitchReel.tsx
15
+ * Bundled at: 2026-05-08T18:50:39.530Z
16
+ */
17
+ import React from "react";
18
+ import {
19
+ useCurrentFrame,
20
+ useVideoConfig,
21
+ interpolate,
22
+ spring,
23
+ Easing,
24
+ AbsoluteFill,
25
+ staticFile,
26
+ Sequence,
27
+ OffthreadVideo,
28
+ Img,
29
+ } from "remotion";
30
+ import { Audio } from "@remotion/media";
31
+ import { ds } from "./designSystem";
32
+
33
+ // ═══════════════════════════════════════════════════════════════
34
+ // TOKENS — dark cinematic + Claude terracotta × Stitch rose
35
+ // ═══════════════════════════════════════════════════════════════
36
+
37
+ const FONT = ds.font.sans;
38
+ const MONO = ds.font.mono;
39
+
40
+ const C = {
41
+ bg: "#0a0a0b",
42
+ bgLift: "#141416",
43
+ surface: "#1a1a1d",
44
+ surfaceLift: "#222226",
45
+ border: "rgba(255,255,255,0.08)",
46
+ borderLoud: "rgba(255,255,255,0.14)",
47
+ fg: "#f5f5f7",
48
+ fgSoft: "#d1d1d6",
49
+ fgMuted: "#8e8e93",
50
+ fgDim: "#5a5a60",
51
+ claude: "#D4663A",
52
+ claudeSoft: "#e07a54",
53
+ claudeDim: "rgba(212,102,58,0.15)",
54
+ stitch: "#ff4d9b",
55
+ stitchSoft: "#ff7fb8",
56
+ stitchDim: "rgba(255,77,155,0.15)",
57
+ gemini: "#4285f4",
58
+ geminiDim: "rgba(66,133,244,0.18)",
59
+ safe: "#4fc46a",
60
+ safeDim: "rgba(79,196,106,0.12)",
61
+ danger: "#e25822",
62
+ amber: "#fcbb00",
63
+ violet: "#8d54ff",
64
+ } as const;
65
+
66
+ // Scene timeline — 76.56s voiceover, 8 segments with overlapping crossfades
67
+ const SEGS = [
68
+ { s: 0, e: 8.3, name: "Hook" },
69
+ { s: 7.9, e: 14.6, name: "Stack" },
70
+ { s: 14.1, e: 22.6, name: "Features" },
71
+ { s: 22.1, e: 30.5, name: "Unlock" },
72
+ { s: 30, e: 44, name: "Magic" },
73
+ { s: 43.5, e: 52, name: "Clone" },
74
+ { s: 51.5, e: 67.5, name: "Test" },
75
+ { s: 67, e: 76.56, name: "CTA" },
76
+ ] as const;
77
+
78
+ // ═══════════════════════════════════════════════════════════════
79
+ // UTILITIES
80
+ // ═══════════════════════════════════════════════════════════════
81
+
82
+ const so = (
83
+ frame: number,
84
+ fps: number,
85
+ startS: number,
86
+ endS: number,
87
+ fadeIn = 0.45,
88
+ fadeOut = 0.55,
89
+ ) =>
90
+ interpolate(
91
+ frame,
92
+ [startS * fps, (startS + fadeIn) * fps, (endS - fadeOut) * fps, endS * fps],
93
+ [0, 1, 1, 0],
94
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" },
95
+ );
96
+
97
+ const lf = (frame: number, fps: number, startSec: number) =>
98
+ Math.max(0, frame - startSec * fps);
99
+
100
+ const gsapSpring = (
101
+ f: number,
102
+ fps: number,
103
+ delaySec = 0,
104
+ kind: "bouncy" | "snappy" | "gentle" | "glass" = "snappy",
105
+ ) =>
106
+ spring({
107
+ frame: f,
108
+ fps,
109
+ delay: Math.round(delaySec * fps),
110
+ config: ds.spring[kind],
111
+ });
112
+
113
+ // GSAP back.out(1.7) approximation
114
+ const backOut = (t: number) => {
115
+ const c1 = 1.70158;
116
+ const c3 = c1 + 1;
117
+ return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
118
+ };
119
+
120
+ interface SP {
121
+ frame: number;
122
+ fps: number;
123
+ }
124
+
125
+ // ═══════════════════════════════════════════════════════════════
126
+ // BACKGROUND — dark cinematic: dual spotlights + grid + grain
127
+ // ═══════════════════════════════════════════════════════════════
128
+
129
+ const Background: React.FC<{ frame: number; fps: number }> = ({ frame }) => {
130
+ const drift = (Math.sin(frame * 0.004) + 1) * 0.5;
131
+ const drift2 = (Math.cos(frame * 0.0032) + 1) * 0.5;
132
+ const vignettePulse = 0.38 + Math.sin(frame * 0.008) * 0.04;
133
+
134
+ return (
135
+ <AbsoluteFill>
136
+ <div style={{ width: "100%", height: "100%", background: C.bg }} />
137
+ {/* Claude terracotta spotlight */}
138
+ <div
139
+ style={{
140
+ position: "absolute",
141
+ inset: 0,
142
+ background: `radial-gradient(ellipse 780px 880px at ${18 + drift * 60}% ${14 + drift2 * 40}%, rgba(212,102,58,0.13) 0%, transparent 60%)`,
143
+ }}
144
+ />
145
+ {/* Stitch rose counter-spot */}
146
+ <div
147
+ style={{
148
+ position: "absolute",
149
+ inset: 0,
150
+ background: `radial-gradient(ellipse 640px 820px at ${82 - drift * 50}% ${78 + drift2 * 20}%, rgba(255,77,155,0.12) 0%, transparent 58%)`,
151
+ }}
152
+ />
153
+ {/* Grid lines */}
154
+ <div
155
+ style={{
156
+ position: "absolute",
157
+ inset: 0,
158
+ backgroundImage: `
159
+ linear-gradient(rgba(255,255,255,0.035) 1px, transparent 1px),
160
+ linear-gradient(90deg, rgba(255,255,255,0.035) 1px, transparent 1px)
161
+ `,
162
+ backgroundSize: "72px 72px",
163
+ maskImage: "radial-gradient(ellipse at 50% 50%, black 30%, transparent 85%)",
164
+ WebkitMaskImage: "radial-gradient(ellipse at 50% 50%, black 30%, transparent 85%)",
165
+ }}
166
+ />
167
+ {/* Noise grain */}
168
+ <div
169
+ style={{
170
+ position: "absolute",
171
+ inset: 0,
172
+ opacity: 0.06,
173
+ mixBlendMode: "overlay",
174
+ backgroundImage: `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='200' height='200' filter='url(%23n)' opacity='0.7'/></svg>")`,
175
+ }}
176
+ />
177
+ {/* Vignette */}
178
+ <div
179
+ style={{
180
+ position: "absolute",
181
+ inset: 0,
182
+ background: `radial-gradient(ellipse at 50% 50%, transparent 40%, rgba(0,0,0,${vignettePulse}) 100%)`,
183
+ }}
184
+ />
185
+ </AbsoluteFill>
186
+ );
187
+ };
188
+
189
+ // ═══════════════════════════════════════════════════════════════
190
+ // SAFE ZONE — IG reel chrome reserves top 288 / bottom 422
191
+ // ═══════════════════════════════════════════════════════════════
192
+
193
+ const SafeZone: React.FC<{ children: React.ReactNode; style?: React.CSSProperties }> = ({
194
+ children,
195
+ style,
196
+ }) => (
197
+ <div
198
+ style={{
199
+ position: "absolute",
200
+ top: 290,
201
+ left: 0,
202
+ right: 0,
203
+ bottom: 430,
204
+ ...style,
205
+ }}
206
+ >
207
+ {children}
208
+ </div>
209
+ );
210
+
211
+ // ═══════════════════════════════════════════════════════════════
212
+ // PRIMITIVES — logos, pills, chrome, kinetic text
213
+ // ═══════════════════════════════════════════════════════════════
214
+
215
+ const StitchGlyph: React.FC<{ size?: number; color?: string; style?: React.CSSProperties }> = ({
216
+ size = 96,
217
+ color = C.stitch,
218
+ style,
219
+ }) => (
220
+ <svg
221
+ width={size}
222
+ height={size}
223
+ viewBox="0 0 100 100"
224
+ style={{ filter: `drop-shadow(0 0 ${size * 0.35}px ${color}55)`, ...style }}
225
+ >
226
+ <rect
227
+ x="10"
228
+ y="10"
229
+ width="80"
230
+ height="80"
231
+ rx="22"
232
+ fill="none"
233
+ stroke={color}
234
+ strokeWidth="4"
235
+ strokeDasharray="8 6"
236
+ />
237
+ <circle cx="50" cy="50" r="14" fill={color} />
238
+ <circle cx="26" cy="26" r="3.5" fill={color} />
239
+ <circle cx="74" cy="26" r="3.5" fill={color} />
240
+ <circle cx="26" cy="74" r="3.5" fill={color} />
241
+ <circle cx="74" cy="74" r="3.5" fill={color} />
242
+ </svg>
243
+ );
244
+
245
+ const ClaudeGlyph: React.FC<{ size?: number; color?: string; style?: React.CSSProperties }> = ({
246
+ size = 96,
247
+ color = C.claude,
248
+ style,
249
+ }) => (
250
+ <svg
251
+ width={size}
252
+ height={size}
253
+ viewBox="0 0 24 24"
254
+ style={{ filter: `drop-shadow(0 0 ${size * 0.3}px ${color}55)`, ...style }}
255
+ >
256
+ <path
257
+ fill={color}
258
+ d="M17.304 3.541h-3.672l6.696 16.918H24Zm-10.608 0L0 20.459h3.744l1.37-3.553h7.005l1.369 3.553h3.744L10.536 3.541Zm-.371 10.223L8.616 7.82l2.291 5.945Z"
259
+ />
260
+ </svg>
261
+ );
262
+
263
+ const GeminiGlyph: React.FC<{ size?: number; style?: React.CSSProperties }> = ({
264
+ size = 64,
265
+ style,
266
+ }) => (
267
+ <svg width={size} height={size} viewBox="0 0 28 28" style={style}>
268
+ <defs>
269
+ <linearGradient id="gemini-grad" x1="0%" y1="0%" x2="100%" y2="100%">
270
+ <stop offset="0%" stopColor="#4285f4" />
271
+ <stop offset="55%" stopColor="#9b72cb" />
272
+ <stop offset="100%" stopColor="#d96570" />
273
+ </linearGradient>
274
+ </defs>
275
+ <path
276
+ d="M14 0c0 7.732 6.268 14 14 14-7.732 0-14 6.268-14 14 0-7.732-6.268-14-14-14C7.732 14 14 7.732 14 0Z"
277
+ fill="url(#gemini-grad)"
278
+ />
279
+ </svg>
280
+ );
281
+
282
+ const PillBadge: React.FC<{
283
+ children: React.ReactNode;
284
+ dotColor?: string;
285
+ pulseDot?: number;
286
+ style?: React.CSSProperties;
287
+ }> = ({ children, dotColor = C.stitch, pulseDot = 1, style }) => (
288
+ <div
289
+ style={{
290
+ display: "inline-flex",
291
+ alignItems: "center",
292
+ gap: 12,
293
+ padding: "10px 22px",
294
+ background: "rgba(255,255,255,0.06)",
295
+ border: `1px solid ${C.borderLoud}`,
296
+ borderRadius: 9999,
297
+ backdropFilter: "blur(10px)",
298
+ ...style,
299
+ }}
300
+ >
301
+ <div
302
+ style={{
303
+ width: 8,
304
+ height: 8,
305
+ borderRadius: "50%",
306
+ background: dotColor,
307
+ boxShadow: `0 0 ${10 + pulseDot * 10}px ${dotColor}`,
308
+ opacity: pulseDot,
309
+ }}
310
+ />
311
+ <span
312
+ style={{
313
+ fontFamily: MONO,
314
+ fontSize: 15,
315
+ fontWeight: 500,
316
+ color: C.fgSoft,
317
+ letterSpacing: "0.18em",
318
+ }}
319
+ >
320
+ {children}
321
+ </span>
322
+ </div>
323
+ );
324
+
325
+ const BrowserChrome: React.FC<{
326
+ children: React.ReactNode;
327
+ width: number;
328
+ height: number;
329
+ url?: string;
330
+ style?: React.CSSProperties;
331
+ }> = ({ children, width, height, url = "stitch.withgoogle.com", style }) => (
332
+ <div
333
+ style={{
334
+ width,
335
+ height,
336
+ borderRadius: 20,
337
+ background: C.surfaceLift,
338
+ border: `1px solid ${C.borderLoud}`,
339
+ boxShadow: [
340
+ "0 40px 90px -20px rgba(0,0,0,0.7)",
341
+ "0 20px 40px -15px rgba(255,77,155,0.14)",
342
+ "inset 0 1px 0 rgba(255,255,255,0.06)",
343
+ ].join(", "),
344
+ overflow: "hidden",
345
+ ...style,
346
+ }}
347
+ >
348
+ <div
349
+ style={{
350
+ height: 42,
351
+ background: C.surface,
352
+ borderBottom: `1px solid ${C.border}`,
353
+ display: "flex",
354
+ alignItems: "center",
355
+ padding: "0 18px",
356
+ gap: 16,
357
+ }}
358
+ >
359
+ <div style={{ display: "flex", gap: 7 }}>
360
+ <div style={{ width: 11, height: 11, borderRadius: "50%", background: "#ff5f57" }} />
361
+ <div style={{ width: 11, height: 11, borderRadius: "50%", background: "#ffbd2e" }} />
362
+ <div style={{ width: 11, height: 11, borderRadius: "50%", background: "#28ca42" }} />
363
+ </div>
364
+ <div
365
+ style={{
366
+ flex: 1,
367
+ height: 24,
368
+ borderRadius: 6,
369
+ background: C.bg,
370
+ display: "flex",
371
+ alignItems: "center",
372
+ padding: "0 10px",
373
+ fontFamily: MONO,
374
+ fontSize: 12,
375
+ color: C.fgMuted,
376
+ letterSpacing: "0.02em",
377
+ }}
378
+ >
379
+ {url}
380
+ </div>
381
+ </div>
382
+ <div style={{ width: "100%", height: height - 42, overflow: "hidden" }}>{children}</div>
383
+ </div>
384
+ );
385
+
386
+ const KineticText: React.FC<{
387
+ text: string;
388
+ f: number;
389
+ fps: number;
390
+ startSec?: number;
391
+ wordStagger?: number;
392
+ size: number;
393
+ weight?: number;
394
+ color?: string;
395
+ mono?: boolean;
396
+ style?: React.CSSProperties;
397
+ }> = ({
398
+ text,
399
+ f,
400
+ fps,
401
+ startSec = 0,
402
+ wordStagger = 0.06,
403
+ size,
404
+ weight = 700,
405
+ color = C.fg,
406
+ mono = false,
407
+ style,
408
+ }) => {
409
+ const words = text.split(" ");
410
+ return (
411
+ <div
412
+ style={{
413
+ display: "flex",
414
+ flexWrap: "wrap",
415
+ gap: "0 0.32em",
416
+ justifyContent: "center",
417
+ fontFamily: mono ? MONO : FONT,
418
+ fontSize: size,
419
+ fontWeight: weight,
420
+ color,
421
+ letterSpacing: "-0.03em",
422
+ lineHeight: 1.04,
423
+ ...style,
424
+ }}
425
+ >
426
+ {words.map((w, i) => {
427
+ const s = gsapSpring(f, fps, startSec + i * wordStagger, "snappy");
428
+ const y = interpolate(s, [0, 1], [40, 0]);
429
+ const op = interpolate(s, [0, 0.4], [0, 1], {
430
+ extrapolateLeft: "clamp",
431
+ extrapolateRight: "clamp",
432
+ });
433
+ return (
434
+ <span
435
+ key={`${w}-${i}`}
436
+ style={{ display: "inline-block", transform: `translateY(${y}px)`, opacity: op }}
437
+ >
438
+ {w}
439
+ </span>
440
+ );
441
+ })}
442
+ </div>
443
+ );
444
+ };
445
+
446
+ const Typewriter: React.FC<{
447
+ text: string;
448
+ f: number;
449
+ fps: number;
450
+ startSec: number;
451
+ cps?: number;
452
+ caret?: boolean;
453
+ style?: React.CSSProperties;
454
+ }> = ({ text, f, fps, startSec, cps = 55, caret = true, style }) => {
455
+ const elapsed = Math.max(0, (f - startSec * fps) / fps);
456
+ const chars = Math.min(text.length, Math.floor(elapsed * cps));
457
+ const typed = text.slice(0, chars);
458
+ const blink = Math.floor(f / 10) % 2 === 0;
459
+ const done = chars >= text.length;
460
+ return (
461
+ <span style={style}>
462
+ {typed}
463
+ {caret && !done && blink ? <span style={{ opacity: 0.85 }}>▍</span> : null}
464
+ </span>
465
+ );
466
+ };
467
+
468
+ // ═══════════════════════════════════════════════════════════════
469
+ // SCENE 1 — HOOK (0 → 8.3s)
470
+ // "Google Stitch plugs into Claude Code, saves tokens. This changes everything."
471
+ // ═══════════════════════════════════════════════════════════════
472
+
473
+ const HookScene: React.FC<SP> = ({ frame, fps }) => {
474
+ const op = so(frame, fps, SEGS[0].s, SEGS[0].e);
475
+ if (op === 0) return null;
476
+ const f = lf(frame, fps, SEGS[0].s);
477
+
478
+ // Top pill
479
+ const pillSpring = gsapSpring(f, fps, 0.1, "bouncy");
480
+ const pillPulse = 0.6 + Math.sin(f * 0.15) * 0.4;
481
+
482
+ // Logo dock — Stitch flies from left, Claude from right, meet at center
483
+ const dockProg = interpolate(f, [0.3 * fps, 1.8 * fps], [0, 1], {
484
+ extrapolateLeft: "clamp",
485
+ extrapolateRight: "clamp",
486
+ easing: Easing.bezier(0.25, 1, 0.5, 1),
487
+ });
488
+ const dockX = interpolate(dockProg, [0, 1], [320, 0]);
489
+ const dockOp = interpolate(dockProg, [0, 0.3], [0, 1], {
490
+ extrapolateLeft: "clamp",
491
+ extrapolateRight: "clamp",
492
+ });
493
+ // Impact flash at dock moment
494
+ const impact = interpolate(f, [1.7 * fps, 2.0 * fps, 2.4 * fps], [0, 1, 0], {
495
+ extrapolateLeft: "clamp",
496
+ extrapolateRight: "clamp",
497
+ });
498
+
499
+ // Big wordmark — smash in after dock
500
+ const smashSpring = gsapSpring(f, fps, 2.1, "bouncy");
501
+ const smashScale = interpolate(smashSpring, [0, 1], [1.5, 1]);
502
+ const smashOp = interpolate(smashSpring, [0, 0.3], [0, 1], {
503
+ extrapolateLeft: "clamp",
504
+ extrapolateRight: "clamp",
505
+ });
506
+ const shakeT = interpolate(f, [2.1 * fps, 2.7 * fps], [0, 1], {
507
+ extrapolateLeft: "clamp",
508
+ extrapolateRight: "clamp",
509
+ });
510
+ const shakeX = Math.sin(shakeT * 22) * (1 - shakeT) * 5;
511
+
512
+ // "TOKEN SAVER" subtitle — after wordmark
513
+ const subSpring = gsapSpring(f, fps, 3.2, "gentle");
514
+ const subOp = interpolate(subSpring, [0, 0.5], [0, 1], {
515
+ extrapolateLeft: "clamp",
516
+ extrapolateRight: "clamp",
517
+ });
518
+
519
+ // Slam closer "THIS CHANGES EVERYTHING"
520
+ const closeT = interpolate(f, [5.8 * fps, 6.3 * fps], [0, 1], {
521
+ extrapolateLeft: "clamp",
522
+ extrapolateRight: "clamp",
523
+ });
524
+ const closeScale = 0.85 + backOut(closeT) * 0.15;
525
+ const closeOp = closeT;
526
+
527
+ return (
528
+ <AbsoluteFill style={{ opacity: op }}>
529
+ <SafeZone>
530
+ {/* Top pill */}
531
+ <div
532
+ style={{
533
+ position: "absolute",
534
+ top: 10,
535
+ left: "50%",
536
+ transform: `translate(-50%, 0) scale(${interpolate(pillSpring, [0, 1], [0.6, 1])})`,
537
+ opacity: interpolate(pillSpring, [0, 0.3], [0, 1], {
538
+ extrapolateLeft: "clamp",
539
+ extrapolateRight: "clamp",
540
+ }),
541
+ }}
542
+ >
543
+ <PillBadge dotColor={C.stitch} pulseDot={pillPulse}>
544
+ GOOGLE STITCH × CLAUDE CODE
545
+ </PillBadge>
546
+ </div>
547
+
548
+ {/* Logo dock — Stitch + × + Claude */}
549
+ <div
550
+ style={{
551
+ position: "absolute",
552
+ top: 120,
553
+ left: 0,
554
+ right: 0,
555
+ display: "flex",
556
+ justifyContent: "center",
557
+ alignItems: "center",
558
+ gap: 48,
559
+ opacity: dockOp,
560
+ }}
561
+ >
562
+ <div style={{ transform: `translateX(${-dockX}px)` }}>
563
+ <StitchGlyph size={128} />
564
+ </div>
565
+ <div
566
+ style={{
567
+ fontFamily: FONT,
568
+ fontSize: 80,
569
+ fontWeight: 300,
570
+ color: C.fgMuted,
571
+ opacity: interpolate(dockProg, [0.6, 1], [0, 1], {
572
+ extrapolateLeft: "clamp",
573
+ extrapolateRight: "clamp",
574
+ }),
575
+ transform: `scale(${interpolate(impact, [0, 1], [1, 1.25])})`,
576
+ }}
577
+ >
578
+ +
579
+ </div>
580
+ <div style={{ transform: `translateX(${dockX}px)` }}>
581
+ <ClaudeGlyph size={128} />
582
+ </div>
583
+ </div>
584
+
585
+ {/* Impact flash ring */}
586
+ <div
587
+ style={{
588
+ position: "absolute",
589
+ top: 148,
590
+ left: "50%",
591
+ transform: `translate(-50%, 0) scale(${1 + impact * 1.8})`,
592
+ width: 140,
593
+ height: 140,
594
+ borderRadius: "50%",
595
+ border: `2px solid ${C.stitch}`,
596
+ opacity: impact * 0.7,
597
+ pointerEvents: "none",
598
+ }}
599
+ />
600
+
601
+ {/* Big wordmark */}
602
+ <div
603
+ style={{
604
+ position: "absolute",
605
+ top: 350,
606
+ left: 0,
607
+ right: 0,
608
+ textAlign: "center",
609
+ transform: `scale(${smashScale}) translateX(${shakeX}px)`,
610
+ opacity: smashOp,
611
+ }}
612
+ >
613
+ <div
614
+ style={{
615
+ fontFamily: FONT,
616
+ fontSize: 138,
617
+ fontWeight: 700,
618
+ color: C.fg,
619
+ letterSpacing: "-0.055em",
620
+ lineHeight: 0.92,
621
+ }}
622
+ >
623
+ plug Stitch
624
+ </div>
625
+ <div
626
+ style={{
627
+ fontFamily: FONT,
628
+ fontSize: 138,
629
+ fontWeight: 700,
630
+ color: C.fg,
631
+ letterSpacing: "-0.055em",
632
+ lineHeight: 0.92,
633
+ marginTop: 4,
634
+ }}
635
+ >
636
+ into{" "}
637
+ <span style={{ color: C.claude, textShadow: "0 0 60px rgba(212,102,58,0.45)" }}>
638
+ Claude Code
639
+ </span>
640
+ </div>
641
+ </div>
642
+
643
+ {/* Subtitle */}
644
+ <div
645
+ style={{
646
+ position: "absolute",
647
+ top: 690,
648
+ left: 0,
649
+ right: 0,
650
+ textAlign: "center",
651
+ opacity: subOp,
652
+ }}
653
+ >
654
+ <span
655
+ style={{
656
+ fontFamily: MONO,
657
+ fontSize: 22,
658
+ fontWeight: 500,
659
+ color: C.fgMuted,
660
+ letterSpacing: "0.22em",
661
+ }}
662
+ >
663
+ —— SAVES A LOT OF CLAUDE TOKENS ——
664
+ </span>
665
+ </div>
666
+
667
+ {/* Closer */}
668
+ <div
669
+ style={{
670
+ position: "absolute",
671
+ bottom: 40,
672
+ left: 0,
673
+ right: 0,
674
+ textAlign: "center",
675
+ transform: `scale(${closeScale})`,
676
+ opacity: closeOp,
677
+ }}
678
+ >
679
+ <div
680
+ style={{
681
+ fontFamily: FONT,
682
+ fontSize: 58,
683
+ fontWeight: 700,
684
+ color: C.stitch,
685
+ letterSpacing: "-0.035em",
686
+ lineHeight: 1.02,
687
+ textShadow: "0 0 60px rgba(255,77,155,0.5)",
688
+ }}
689
+ >
690
+ this changes everything.
691
+ </div>
692
+ </div>
693
+ </SafeZone>
694
+ </AbsoluteFill>
695
+ );
696
+ };
697
+
698
+ // ═══════════════════════════════════════════════════════════════
699
+ // SCENE 2 — STACK (7.9 → 14.6s)
700
+ // "Powered by Gemini 3.1. One-click install. Your coding agent can design."
701
+ // ═══════════════════════════════════════════════════════════════
702
+
703
+ const StackScene: React.FC<SP> = ({ frame, fps }) => {
704
+ const op = so(frame, fps, SEGS[1].s, SEGS[1].e);
705
+ if (op === 0) return null;
706
+ const f = lf(frame, fps, SEGS[1].s);
707
+
708
+ // "POWERED BY" eyebrow
709
+ const eyebrowS = gsapSpring(f, fps, 0.1, "gentle");
710
+ // Gemini badge slam
711
+ const gemS = gsapSpring(f, fps, 0.4, "bouncy");
712
+ const gemScale = interpolate(gemS, [0, 1], [1.4, 1]);
713
+ const gemOp = interpolate(gemS, [0, 0.3], [0, 1], {
714
+ extrapolateLeft: "clamp",
715
+ extrapolateRight: "clamp",
716
+ });
717
+
718
+ // Target install cards
719
+ const card1 = gsapSpring(f, fps, 1.6, "snappy");
720
+ const card2 = gsapSpring(f, fps, 1.8, "snappy");
721
+
722
+ // Terminal typewriter
723
+ const termS = gsapSpring(f, fps, 2.6, "glass");
724
+ const termOp = interpolate(termS, [0, 0.4], [0, 1], {
725
+ extrapolateLeft: "clamp",
726
+ extrapolateRight: "clamp",
727
+ });
728
+ const termY = interpolate(termS, [0, 1], [30, 0]);
729
+ const checkT = interpolate(f, [4.2 * fps, 4.8 * fps], [0, 1], {
730
+ extrapolateLeft: "clamp",
731
+ extrapolateRight: "clamp",
732
+ });
733
+
734
+ // Bottom tagline
735
+ const tagOp = interpolate(f, [4.8 * fps, 5.4 * fps], [0, 1], {
736
+ extrapolateLeft: "clamp",
737
+ extrapolateRight: "clamp",
738
+ });
739
+
740
+ return (
741
+ <AbsoluteFill style={{ opacity: op }}>
742
+ <SafeZone>
743
+ {/* Eyebrow */}
744
+ <div
745
+ style={{
746
+ position: "absolute",
747
+ top: 10,
748
+ left: 0,
749
+ right: 0,
750
+ textAlign: "center",
751
+ opacity: interpolate(eyebrowS, [0, 0.5], [0, 1], {
752
+ extrapolateLeft: "clamp",
753
+ extrapolateRight: "clamp",
754
+ }),
755
+ }}
756
+ >
757
+ <span
758
+ style={{
759
+ fontFamily: MONO,
760
+ fontSize: 18,
761
+ fontWeight: 500,
762
+ color: C.fgMuted,
763
+ letterSpacing: "0.28em",
764
+ }}
765
+ >
766
+ POWERED BY
767
+ </span>
768
+ </div>
769
+
770
+ {/* Gemini 3.1 huge badge */}
771
+ <div
772
+ style={{
773
+ position: "absolute",
774
+ top: 60,
775
+ left: 0,
776
+ right: 0,
777
+ display: "flex",
778
+ justifyContent: "center",
779
+ alignItems: "center",
780
+ gap: 22,
781
+ transform: `scale(${gemScale})`,
782
+ opacity: gemOp,
783
+ }}
784
+ >
785
+ <GeminiGlyph size={84} />
786
+ <div
787
+ style={{
788
+ fontFamily: FONT,
789
+ fontSize: 110,
790
+ fontWeight: 700,
791
+ letterSpacing: "-0.045em",
792
+ background: "linear-gradient(92deg, #4285f4, #9b72cb 55%, #d96570)",
793
+ WebkitBackgroundClip: "text",
794
+ WebkitTextFillColor: "transparent",
795
+ backgroundClip: "text",
796
+ }}
797
+ >
798
+ Gemini 3.1
799
+ </div>
800
+ </div>
801
+
802
+ {/* One-click install header */}
803
+ <div
804
+ style={{
805
+ position: "absolute",
806
+ top: 250,
807
+ left: 0,
808
+ right: 0,
809
+ textAlign: "center",
810
+ opacity: interpolate(f, [1.2 * fps, 1.6 * fps], [0, 1], {
811
+ extrapolateLeft: "clamp",
812
+ extrapolateRight: "clamp",
813
+ }),
814
+ }}
815
+ >
816
+ <span
817
+ style={{
818
+ fontFamily: FONT,
819
+ fontSize: 40,
820
+ fontWeight: 600,
821
+ color: C.fgSoft,
822
+ letterSpacing: "-0.025em",
823
+ }}
824
+ >
825
+ one-click install into
826
+ </span>
827
+ </div>
828
+
829
+ {/* Two target cards */}
830
+ <div
831
+ style={{
832
+ position: "absolute",
833
+ top: 320,
834
+ left: 60,
835
+ right: 60,
836
+ display: "grid",
837
+ gridTemplateColumns: "1fr 1fr",
838
+ gap: 20,
839
+ }}
840
+ >
841
+ {[
842
+ { key: "cc", label: "Claude Code", color: C.claude, dot: C.claude, spring: card1, icon: "cc" },
843
+ { key: "ag", label: "Antigravity", color: C.violet, dot: C.violet, spring: card2, icon: "ag" },
844
+ ].map((t) => {
845
+ const s = t.spring;
846
+ const y = interpolate(s, [0, 1], [60, 0]);
847
+ const o = interpolate(s, [0, 0.4], [0, 1], {
848
+ extrapolateLeft: "clamp",
849
+ extrapolateRight: "clamp",
850
+ });
851
+ return (
852
+ <div
853
+ key={t.key}
854
+ style={{
855
+ padding: "24px 20px",
856
+ borderRadius: 20,
857
+ background: C.surface,
858
+ border: `1px solid ${C.border}`,
859
+ borderTop: `3px solid ${t.color}`,
860
+ display: "flex",
861
+ flexDirection: "column",
862
+ alignItems: "center",
863
+ gap: 14,
864
+ transform: `translateY(${y}px)`,
865
+ opacity: o,
866
+ boxShadow: ds.shadow.card,
867
+ }}
868
+ >
869
+ <div
870
+ style={{
871
+ width: 60,
872
+ height: 60,
873
+ borderRadius: 16,
874
+ background: `${t.color}26`,
875
+ border: `1px solid ${t.color}55`,
876
+ display: "flex",
877
+ alignItems: "center",
878
+ justifyContent: "center",
879
+ }}
880
+ >
881
+ {t.icon === "cc" ? (
882
+ <ClaudeGlyph size={32} />
883
+ ) : (
884
+ <svg width="30" height="30" viewBox="0 0 24 24" fill="none">
885
+ <circle cx="12" cy="12" r="9" stroke={t.color} strokeWidth="2" />
886
+ <path d="M12 3v18M3 12h18" stroke={t.color} strokeWidth="2" strokeLinecap="round" />
887
+ </svg>
888
+ )}
889
+ </div>
890
+ <div
891
+ style={{
892
+ fontFamily: FONT,
893
+ fontSize: 24,
894
+ fontWeight: 600,
895
+ color: C.fg,
896
+ letterSpacing: "-0.02em",
897
+ }}
898
+ >
899
+ {t.label}
900
+ </div>
901
+ <div
902
+ style={{
903
+ display: "flex",
904
+ alignItems: "center",
905
+ gap: 8,
906
+ padding: "4px 12px",
907
+ borderRadius: 9999,
908
+ background: `${C.safeDim}`,
909
+ border: `1px solid ${C.safe}55`,
910
+ }}
911
+ >
912
+ <div
913
+ style={{
914
+ width: 6,
915
+ height: 6,
916
+ borderRadius: "50%",
917
+ background: C.safe,
918
+ boxShadow: `0 0 8px ${C.safe}`,
919
+ }}
920
+ />
921
+ <span
922
+ style={{
923
+ fontFamily: MONO,
924
+ fontSize: 12,
925
+ fontWeight: 500,
926
+ color: C.safe,
927
+ letterSpacing: "0.12em",
928
+ }}
929
+ >
930
+ READY
931
+ </span>
932
+ </div>
933
+ </div>
934
+ );
935
+ })}
936
+ </div>
937
+
938
+ {/* Terminal */}
939
+ <div
940
+ style={{
941
+ position: "absolute",
942
+ top: 640,
943
+ left: 60,
944
+ right: 60,
945
+ transform: `translateY(${termY}px)`,
946
+ opacity: termOp,
947
+ }}
948
+ >
949
+ <div
950
+ style={{
951
+ borderRadius: 16,
952
+ background: C.bg,
953
+ border: `1px solid ${C.borderLoud}`,
954
+ padding: "18px 24px",
955
+ fontFamily: MONO,
956
+ fontSize: 22,
957
+ color: C.fg,
958
+ letterSpacing: 0,
959
+ lineHeight: 1.6,
960
+ boxShadow: "0 20px 50px -10px rgba(0,0,0,0.6)",
961
+ }}
962
+ >
963
+ <div style={{ color: C.fgDim }}>
964
+ <span style={{ color: C.stitch }}>$</span>{" "}
965
+ <Typewriter text="claude plugin install stitch" f={f} fps={fps} startSec={2.8} cps={28} />
966
+ </div>
967
+ <div
968
+ style={{
969
+ color: C.safe,
970
+ opacity: checkT,
971
+ transform: `translateY(${interpolate(checkT, [0, 1], [10, 0])}px)`,
972
+ display: "flex",
973
+ alignItems: "center",
974
+ gap: 10,
975
+ marginTop: 4,
976
+ }}
977
+ >
978
+ <span>✓</span>
979
+ <span style={{ color: C.fgSoft }}>installed — ready to design</span>
980
+ </div>
981
+ </div>
982
+ </div>
983
+
984
+ {/* Tagline */}
985
+ <div
986
+ style={{
987
+ position: "absolute",
988
+ bottom: 10,
989
+ left: 0,
990
+ right: 0,
991
+ textAlign: "center",
992
+ opacity: tagOp,
993
+ }}
994
+ >
995
+ <span
996
+ style={{
997
+ fontFamily: FONT,
998
+ fontSize: 40,
999
+ fontWeight: 600,
1000
+ color: C.fgSoft,
1001
+ letterSpacing: "-0.025em",
1002
+ }}
1003
+ >
1004
+ your coding agent can{" "}
1005
+ <span style={{ color: C.stitch, fontStyle: "italic" }}>design</span>.
1006
+ </span>
1007
+ </div>
1008
+ </SafeZone>
1009
+ </AbsoluteFill>
1010
+ );
1011
+ };
1012
+
1013
+ // ═══════════════════════════════════════════════════════════════
1014
+ // SCENE 3 — FEATURES (14.1 → 22.6s)
1015
+ // "Prompt a full website. Edit text on canvas. Drop a URL → Nano Banana 2."
1016
+ // ═══════════════════════════════════════════════════════════════
1017
+
1018
+ const FEATURES = [
1019
+ { t: 0.2, dur: 2.5, title: "prompt a full website", mono: "→ generate.stitch", icon: "prompt" },
1020
+ { t: 2.7, dur: 2.3, title: "edit text on the canvas", mono: "→ canvas.direct", icon: "edit" },
1021
+ { t: 5.0, dur: 3.3, title: "drop a URL → redesign", mono: "→ Nano Banana 2", icon: "url" },
1022
+ ] as const;
1023
+
1024
+ const FeaturesScene: React.FC<SP> = ({ frame, fps }) => {
1025
+ const op = so(frame, fps, SEGS[2].s, SEGS[2].e);
1026
+ if (op === 0) return null;
1027
+ const f = lf(frame, fps, SEGS[2].s);
1028
+ const durFrames = Math.round((SEGS[2].e - SEGS[2].s) * fps);
1029
+
1030
+ // Video chrome entrance
1031
+ const vidS = gsapSpring(f, fps, 0.1, "glass");
1032
+ const vidScale = interpolate(vidS, [0, 1], [0.9, 1]);
1033
+ const vidOp = interpolate(vidS, [0, 0.4], [0, 1], {
1034
+ extrapolateLeft: "clamp",
1035
+ extrapolateRight: "clamp",
1036
+ });
1037
+ // Perspective tilt — steady subtle
1038
+ const tilt = -3 + Math.sin(f * 0.01) * 1;
1039
+
1040
+ return (
1041
+ <AbsoluteFill style={{ opacity: op }}>
1042
+ <SafeZone>
1043
+ {/* Eyebrow pill */}
1044
+ <div
1045
+ style={{
1046
+ position: "absolute",
1047
+ top: 10,
1048
+ left: "50%",
1049
+ transform: "translate(-50%, 0)",
1050
+ opacity: vidOp,
1051
+ }}
1052
+ >
1053
+ <PillBadge dotColor={C.stitch} pulseDot={0.6 + Math.sin(f * 0.15) * 0.4}>
1054
+ STITCH · LIVE DEMO
1055
+ </PillBadge>
1056
+ </div>
1057
+
1058
+ {/* Browser chrome with video */}
1059
+ <div
1060
+ style={{
1061
+ position: "absolute",
1062
+ top: 85,
1063
+ left: 0,
1064
+ right: 0,
1065
+ display: "flex",
1066
+ justifyContent: "center",
1067
+ transform: `scale(${vidScale}) rotateX(${tilt * 0.6}deg) rotateY(${tilt * 0.2}deg)`,
1068
+ opacity: vidOp,
1069
+ perspective: 1400,
1070
+ }}
1071
+ >
1072
+ <BrowserChrome width={960} height={600} url="stitch.withgoogle.com/generate">
1073
+ <Sequence
1074
+ from={Math.round(SEGS[2].s * fps)}
1075
+ durationInFrames={durFrames}
1076
+ >
1077
+ {/* REFERENCE-STRIP: <OffthreadVideo> removed — bring your own clip */}
1078
+ </Sequence>
1079
+ </BrowserChrome>
1080
+ </div>
1081
+
1082
+ {/* Feature callout track */}
1083
+ <div
1084
+ style={{
1085
+ position: "absolute",
1086
+ bottom: 60,
1087
+ left: 60,
1088
+ right: 60,
1089
+ height: 200,
1090
+ }}
1091
+ >
1092
+ {FEATURES.map((feat, i) => {
1093
+ const start = feat.t;
1094
+ const end = feat.t + feat.dur;
1095
+ const inS = gsapSpring(f, fps, start, "snappy");
1096
+ const inOp = interpolate(inS, [0, 0.35], [0, 1], {
1097
+ extrapolateLeft: "clamp",
1098
+ extrapolateRight: "clamp",
1099
+ });
1100
+ const outOp = interpolate(f, [(end - 0.4) * fps, end * fps], [1, 0], {
1101
+ extrapolateLeft: "clamp",
1102
+ extrapolateRight: "clamp",
1103
+ });
1104
+ const ox = interpolate(inS, [0, 1], [120, 0]);
1105
+ const o = Math.min(inOp, outOp);
1106
+ if (o <= 0) return null;
1107
+ return (
1108
+ <div
1109
+ key={i}
1110
+ style={{
1111
+ position: "absolute",
1112
+ inset: 0,
1113
+ opacity: o,
1114
+ transform: `translateX(${ox}px)`,
1115
+ }}
1116
+ >
1117
+ <div
1118
+ style={{
1119
+ display: "flex",
1120
+ alignItems: "center",
1121
+ gap: 12,
1122
+ fontFamily: MONO,
1123
+ fontSize: 15,
1124
+ color: C.stitch,
1125
+ letterSpacing: "0.2em",
1126
+ marginBottom: 10,
1127
+ }}
1128
+ >
1129
+ <div
1130
+ style={{
1131
+ width: 26,
1132
+ height: 1,
1133
+ background: C.stitch,
1134
+ boxShadow: `0 0 8px ${C.stitch}`,
1135
+ }}
1136
+ />
1137
+ FEATURE · 0{i + 1} / 03
1138
+ </div>
1139
+ <div
1140
+ style={{
1141
+ fontFamily: FONT,
1142
+ fontSize: 64,
1143
+ fontWeight: 700,
1144
+ color: C.fg,
1145
+ letterSpacing: "-0.035em",
1146
+ lineHeight: 1.02,
1147
+ }}
1148
+ >
1149
+ {feat.title}
1150
+ </div>
1151
+ <div
1152
+ style={{
1153
+ fontFamily: MONO,
1154
+ fontSize: 18,
1155
+ fontWeight: 500,
1156
+ color: C.fgMuted,
1157
+ letterSpacing: "0.08em",
1158
+ marginTop: 10,
1159
+ }}
1160
+ >
1161
+ {feat.mono}
1162
+ </div>
1163
+ </div>
1164
+ );
1165
+ })}
1166
+ </div>
1167
+
1168
+ {/* Progress dots */}
1169
+ <div
1170
+ style={{
1171
+ position: "absolute",
1172
+ bottom: 10,
1173
+ left: "50%",
1174
+ transform: "translate(-50%, 0)",
1175
+ display: "flex",
1176
+ gap: 10,
1177
+ }}
1178
+ >
1179
+ {FEATURES.map((feat, i) => {
1180
+ const start = feat.t;
1181
+ const end = feat.t + feat.dur;
1182
+ const active = f >= start * fps && f < end * fps;
1183
+ return (
1184
+ <div
1185
+ key={i}
1186
+ style={{
1187
+ width: active ? 28 : 10,
1188
+ height: 6,
1189
+ borderRadius: 6,
1190
+ background: active ? C.stitch : C.border,
1191
+ boxShadow: active ? `0 0 10px ${C.stitch}` : "none",
1192
+ transition: "width 200ms",
1193
+ }}
1194
+ />
1195
+ );
1196
+ })}
1197
+ </div>
1198
+ </SafeZone>
1199
+ </AbsoluteFill>
1200
+ );
1201
+ };
1202
+
1203
+ // ═══════════════════════════════════════════════════════════════
1204
+ // SCENE 4 — UNLOCK (22.1 → 30.5s)
1205
+ // "The real unlock — Stitch GitHub repo packed with skills."
1206
+ // ═══════════════════════════════════════════════════════════════
1207
+
1208
+ const UnlockScene: React.FC<SP> = ({ frame, fps }) => {
1209
+ const op = so(frame, fps, SEGS[3].s, SEGS[3].e);
1210
+ if (op === 0) return null;
1211
+ const f = lf(frame, fps, SEGS[3].s);
1212
+
1213
+ // Pivot headline
1214
+ const headS = gsapSpring(f, fps, 0, "bouncy");
1215
+ const headScale = interpolate(headS, [0, 1], [1.3, 1]);
1216
+ const headOp = interpolate(headS, [0, 0.3], [0, 1], {
1217
+ extrapolateLeft: "clamp",
1218
+ extrapolateRight: "clamp",
1219
+ });
1220
+
1221
+ // Git repo image — slow zoom
1222
+ const repoS = gsapSpring(f, fps, 1.2, "glass");
1223
+ const repoOp = interpolate(repoS, [0, 0.4], [0, 1], {
1224
+ extrapolateLeft: "clamp",
1225
+ extrapolateRight: "clamp",
1226
+ });
1227
+ const baseScale = 1 + interpolate(f, [1.5 * fps, (SEGS[3].e - SEGS[3].s) * fps], [0, 0.09], {
1228
+ extrapolateLeft: "clamp",
1229
+ extrapolateRight: "clamp",
1230
+ });
1231
+
1232
+ // Scan line
1233
+ const scanY = ((f * 3) % 700) - 50;
1234
+
1235
+ // Skills folder highlight ring — at 3.5s
1236
+ const ringS = gsapSpring(f, fps, 3.3, "snappy");
1237
+ const ringScale = interpolate(ringS, [0, 1], [1.4, 1]);
1238
+ const ringOp = interpolate(ringS, [0, 0.35], [0, 1], {
1239
+ extrapolateLeft: "clamp",
1240
+ extrapolateRight: "clamp",
1241
+ });
1242
+
1243
+ // Stats strip
1244
+ const statS = gsapSpring(f, fps, 4.3, "snappy");
1245
+ const statOp = interpolate(statS, [0, 0.4], [0, 1], {
1246
+ extrapolateLeft: "clamp",
1247
+ extrapolateRight: "clamp",
1248
+ });
1249
+
1250
+ // Closing line
1251
+ const closeOp = interpolate(f, [5.5 * fps, 6.2 * fps], [0, 1], {
1252
+ extrapolateLeft: "clamp",
1253
+ extrapolateRight: "clamp",
1254
+ });
1255
+ const closeShake = Math.sin(f * 0.4) * 2;
1256
+
1257
+ return (
1258
+ <AbsoluteFill style={{ opacity: op }}>
1259
+ <SafeZone>
1260
+ {/* Pivot */}
1261
+ <div
1262
+ style={{
1263
+ position: "absolute",
1264
+ top: 0,
1265
+ left: 0,
1266
+ right: 0,
1267
+ textAlign: "center",
1268
+ transform: `scale(${headScale})`,
1269
+ opacity: headOp,
1270
+ }}
1271
+ >
1272
+ <div
1273
+ style={{
1274
+ fontFamily: MONO,
1275
+ fontSize: 16,
1276
+ color: C.stitch,
1277
+ letterSpacing: "0.26em",
1278
+ marginBottom: 8,
1279
+ }}
1280
+ >
1281
+ —— THE REAL UNLOCK ——
1282
+ </div>
1283
+ <div
1284
+ style={{
1285
+ fontFamily: FONT,
1286
+ fontSize: 68,
1287
+ fontWeight: 700,
1288
+ color: C.fg,
1289
+ letterSpacing: "-0.04em",
1290
+ lineHeight: 1.0,
1291
+ }}
1292
+ >
1293
+ a GitHub repo{" "}
1294
+ <span style={{ color: C.stitch }}>packed with skills</span>
1295
+ </div>
1296
+ </div>
1297
+
1298
+ {/* Git repo image in browser chrome */}
1299
+ <div
1300
+ style={{
1301
+ position: "absolute",
1302
+ top: 200,
1303
+ left: 0,
1304
+ right: 0,
1305
+ display: "flex",
1306
+ justifyContent: "center",
1307
+ opacity: repoOp,
1308
+ }}
1309
+ >
1310
+ <div
1311
+ style={{
1312
+ transform: `scale(${baseScale})`,
1313
+ transformOrigin: "center center",
1314
+ }}
1315
+ >
1316
+ <BrowserChrome width={960} height={470} url="github.com/Leonxinx/stitch-skills">
1317
+ <div style={{ position: "relative", width: "100%", height: "100%", background: C.bg }}>
1318
+ <Img
1319
+ src={staticFile("captures/your-asset.png" /* REFERENCE-STRIP */)}
1320
+ style={{ width: "100%", height: "100%", objectFit: "contain", objectPosition: "center top" }}
1321
+ />
1322
+ {/* Scan line */}
1323
+ <div
1324
+ style={{
1325
+ position: "absolute",
1326
+ left: 0,
1327
+ right: 0,
1328
+ top: scanY,
1329
+ height: 80,
1330
+ background: `linear-gradient(180deg, transparent, rgba(255,77,155,0.22), transparent)`,
1331
+ pointerEvents: "none",
1332
+ }}
1333
+ />
1334
+ {/* Skills folder ring */}
1335
+ <div
1336
+ style={{
1337
+ position: "absolute",
1338
+ left: 108,
1339
+ top: 110,
1340
+ width: 130,
1341
+ height: 28,
1342
+ borderRadius: 8,
1343
+ border: `2px solid ${C.stitch}`,
1344
+ opacity: ringOp,
1345
+ transform: `scale(${ringScale})`,
1346
+ transformOrigin: "center center",
1347
+ boxShadow: `0 0 30px ${C.stitch}80, inset 0 0 20px ${C.stitchDim}`,
1348
+ pointerEvents: "none",
1349
+ }}
1350
+ />
1351
+ {/* Arrow callout to skills */}
1352
+ <div
1353
+ style={{
1354
+ position: "absolute",
1355
+ left: 252,
1356
+ top: 112,
1357
+ opacity: ringOp,
1358
+ display: "flex",
1359
+ alignItems: "center",
1360
+ gap: 10,
1361
+ }}
1362
+ >
1363
+ <svg width="40" height="24" viewBox="0 0 40 24">
1364
+ <path
1365
+ d="M38 12 H4 M10 6 L4 12 L10 18"
1366
+ stroke={C.stitch}
1367
+ strokeWidth="2.5"
1368
+ strokeLinecap="round"
1369
+ strokeLinejoin="round"
1370
+ fill="none"
1371
+ />
1372
+ </svg>
1373
+ <div
1374
+ style={{
1375
+ fontFamily: MONO,
1376
+ fontSize: 15,
1377
+ fontWeight: 600,
1378
+ color: C.stitch,
1379
+ letterSpacing: "0.2em",
1380
+ background: C.bg,
1381
+ border: `1px solid ${C.stitch}`,
1382
+ padding: "6px 12px",
1383
+ borderRadius: 8,
1384
+ }}
1385
+ >
1386
+ SKILLS FOLDER
1387
+ </div>
1388
+ </div>
1389
+ </div>
1390
+ </BrowserChrome>
1391
+ </div>
1392
+ </div>
1393
+
1394
+ {/* Stats strip */}
1395
+ <div
1396
+ style={{
1397
+ position: "absolute",
1398
+ bottom: 80,
1399
+ left: 0,
1400
+ right: 0,
1401
+ display: "flex",
1402
+ justifyContent: "center",
1403
+ gap: 18,
1404
+ opacity: statOp,
1405
+ }}
1406
+ >
1407
+ {[
1408
+ { n: "4.7K", l: "STARS", c: C.amber },
1409
+ { n: "43", l: "WATCHING", c: C.stitch },
1410
+ { n: "560", l: "FORKS", c: C.safe },
1411
+ ].map((s, i) => {
1412
+ const d = gsapSpring(f, fps, 4.3 + i * 0.15, "bouncy");
1413
+ const y = interpolate(d, [0, 1], [30, 0]);
1414
+ return (
1415
+ <div
1416
+ key={s.l}
1417
+ style={{
1418
+ padding: "10px 22px",
1419
+ background: C.surface,
1420
+ border: `1px solid ${s.c}55`,
1421
+ borderRadius: 14,
1422
+ display: "flex",
1423
+ alignItems: "baseline",
1424
+ gap: 8,
1425
+ transform: `translateY(${y}px)`,
1426
+ }}
1427
+ >
1428
+ <span
1429
+ style={{
1430
+ fontFamily: FONT,
1431
+ fontSize: 28,
1432
+ fontWeight: 700,
1433
+ color: s.c,
1434
+ letterSpacing: "-0.02em",
1435
+ }}
1436
+ >
1437
+ {s.n}
1438
+ </span>
1439
+ <span
1440
+ style={{
1441
+ fontFamily: MONO,
1442
+ fontSize: 12,
1443
+ fontWeight: 500,
1444
+ color: C.fgMuted,
1445
+ letterSpacing: "0.18em",
1446
+ }}
1447
+ >
1448
+ {s.l}
1449
+ </span>
1450
+ </div>
1451
+ );
1452
+ })}
1453
+ </div>
1454
+
1455
+ {/* Magic closing line */}
1456
+ <div
1457
+ style={{
1458
+ position: "absolute",
1459
+ bottom: 0,
1460
+ left: 0,
1461
+ right: 0,
1462
+ textAlign: "center",
1463
+ opacity: closeOp,
1464
+ }}
1465
+ >
1466
+ <span
1467
+ style={{
1468
+ fontFamily: FONT,
1469
+ fontSize: 42,
1470
+ fontWeight: 700,
1471
+ color: C.fg,
1472
+ letterSpacing: "-0.03em",
1473
+ transform: `translateX(${closeShake}px)`,
1474
+ display: "inline-block",
1475
+ }}
1476
+ >
1477
+ this is where the{" "}
1478
+ <span style={{ color: C.stitch, textShadow: "0 0 40px rgba(255,77,155,0.6)" }}>magic</span>{" "}
1479
+ happens.
1480
+ </span>
1481
+ </div>
1482
+ </SafeZone>
1483
+ </AbsoluteFill>
1484
+ );
1485
+ };
1486
+
1487
+ // ═══════════════════════════════════════════════════════════════
1488
+ // SCENE 5 — MAGIC LOOP (30 → 44s)
1489
+ // Stitch screen → Claude reads → design-system.md blueprint
1490
+ // ═══════════════════════════════════════════════════════════════
1491
+
1492
+ const MagicScene: React.FC<SP> = ({ frame, fps }) => {
1493
+ const op = so(frame, fps, SEGS[4].s, SEGS[4].e);
1494
+ if (op === 0) return null;
1495
+ const f = lf(frame, fps, SEGS[4].s);
1496
+
1497
+ // Phase A (0-3.5s): Stitch screen lights up
1498
+ // Phase B (3.2-7s): Arrow flows to Claude
1499
+ // Phase C (6.5-14s): design-system.md blueprint builds line by line
1500
+
1501
+ const phaseAS = gsapSpring(f, fps, 0.1, "glass");
1502
+ const phaseAOp = interpolate(phaseAS, [0, 0.4], [0, 1], {
1503
+ extrapolateLeft: "clamp",
1504
+ extrapolateRight: "clamp",
1505
+ });
1506
+ const phaseAOut = interpolate(f, [6.5 * fps, 7.3 * fps], [1, 0.4], {
1507
+ extrapolateLeft: "clamp",
1508
+ extrapolateRight: "clamp",
1509
+ });
1510
+
1511
+ // Arrow draw
1512
+ const arrowT = interpolate(f, [3.2 * fps, 5.2 * fps], [0, 1], {
1513
+ extrapolateLeft: "clamp",
1514
+ extrapolateRight: "clamp",
1515
+ easing: Easing.bezier(0.4, 0, 0.2, 1),
1516
+ });
1517
+
1518
+ // Claude pulse on arrival
1519
+ const claudeS = gsapSpring(f, fps, 4.6, "bouncy");
1520
+ const claudeScale = interpolate(claudeS, [0, 1], [0.6, 1]);
1521
+ const claudePulse = 1 + Math.sin(f * 0.25) * 0.04;
1522
+
1523
+ // Blueprint panel
1524
+ const bpS = gsapSpring(f, fps, 6.3, "glass");
1525
+ const bpOp = interpolate(bpS, [0, 0.4], [0, 1], {
1526
+ extrapolateLeft: "clamp",
1527
+ extrapolateRight: "clamp",
1528
+ });
1529
+ const bpY = interpolate(bpS, [0, 1], [50, 0]);
1530
+
1531
+ return (
1532
+ <AbsoluteFill style={{ opacity: op }}>
1533
+ <SafeZone>
1534
+ {/* Eyebrow */}
1535
+ <div
1536
+ style={{
1537
+ position: "absolute",
1538
+ top: 10,
1539
+ left: "50%",
1540
+ transform: "translate(-50%, 0)",
1541
+ opacity: phaseAOp,
1542
+ }}
1543
+ >
1544
+ <PillBadge dotColor={C.stitch} pulseDot={0.6 + Math.sin(f * 0.15) * 0.4}>
1545
+ THE MAGIC LOOP
1546
+ </PillBadge>
1547
+ </div>
1548
+
1549
+ {/* Top row: Stitch screen → arrow → Claude */}
1550
+ <div
1551
+ style={{
1552
+ position: "absolute",
1553
+ top: 80,
1554
+ left: 0,
1555
+ right: 0,
1556
+ display: "flex",
1557
+ alignItems: "center",
1558
+ justifyContent: "center",
1559
+ gap: 24,
1560
+ opacity: phaseAOp,
1561
+ }}
1562
+ >
1563
+ {/* Stitch screen card */}
1564
+ <div
1565
+ style={{
1566
+ opacity: phaseAOut,
1567
+ transform: `scale(${interpolate(phaseAS, [0, 1], [0.9, 1])})`,
1568
+ }}
1569
+ >
1570
+ <BrowserChrome width={420} height={300} url="stitch.withgoogle.com">
1571
+ <div style={{ position: "relative", width: "100%", height: "100%", background: C.bgLift }}>
1572
+ <Sequence
1573
+ from={Math.round(SEGS[4].s * fps)}
1574
+ durationInFrames={Math.round(8 * fps)}
1575
+ >
1576
+ {/* REFERENCE-STRIP: <OffthreadVideo> removed — bring your own clip */}
1577
+ </Sequence>
1578
+ </div>
1579
+ </BrowserChrome>
1580
+ <div
1581
+ style={{
1582
+ marginTop: 12,
1583
+ fontFamily: MONO,
1584
+ fontSize: 14,
1585
+ color: C.stitch,
1586
+ letterSpacing: "0.2em",
1587
+ textAlign: "center",
1588
+ }}
1589
+ >
1590
+ 01 · STITCH SCREEN
1591
+ </div>
1592
+ </div>
1593
+
1594
+ {/* Arrow path */}
1595
+ <svg width="140" height="80" viewBox="0 0 140 80">
1596
+ <defs>
1597
+ <marker
1598
+ id="arrowhead-magic"
1599
+ viewBox="0 0 10 10"
1600
+ refX="9"
1601
+ refY="5"
1602
+ markerWidth="5"
1603
+ markerHeight="5"
1604
+ orient="auto"
1605
+ >
1606
+ <path d="M0,0 L10,5 L0,10 Z" fill={C.fg} />
1607
+ </marker>
1608
+ </defs>
1609
+ <path
1610
+ d="M10 40 Q 70 10 130 40"
1611
+ stroke={C.fg}
1612
+ strokeWidth="2.5"
1613
+ fill="none"
1614
+ strokeDasharray="220"
1615
+ strokeDashoffset={220 - arrowT * 220}
1616
+ markerEnd={arrowT > 0.9 ? "url(#arrowhead-magic)" : undefined}
1617
+ strokeLinecap="round"
1618
+ opacity={arrowT > 0.02 ? 1 : 0}
1619
+ />
1620
+ {/* Sparkle along path */}
1621
+ {arrowT > 0.1 && arrowT < 1 ? (
1622
+ <circle
1623
+ cx={10 + (130 - 10) * arrowT}
1624
+ cy={40 + Math.sin(arrowT * Math.PI) * -20}
1625
+ r="4"
1626
+ fill={C.stitch}
1627
+ style={{ filter: `drop-shadow(0 0 8px ${C.stitch})` }}
1628
+ />
1629
+ ) : null}
1630
+ </svg>
1631
+
1632
+ {/* Claude destination */}
1633
+ <div
1634
+ style={{
1635
+ display: "flex",
1636
+ flexDirection: "column",
1637
+ alignItems: "center",
1638
+ gap: 10,
1639
+ transform: `scale(${claudeScale * claudePulse})`,
1640
+ }}
1641
+ >
1642
+ <div
1643
+ style={{
1644
+ width: 140,
1645
+ height: 140,
1646
+ borderRadius: 32,
1647
+ background: `${C.claudeDim}`,
1648
+ border: `2px solid ${C.claude}`,
1649
+ display: "flex",
1650
+ alignItems: "center",
1651
+ justifyContent: "center",
1652
+ boxShadow: `0 0 60px rgba(212,102,58,0.45)`,
1653
+ }}
1654
+ >
1655
+ <ClaudeGlyph size={72} />
1656
+ </div>
1657
+ <div
1658
+ style={{
1659
+ fontFamily: MONO,
1660
+ fontSize: 14,
1661
+ color: C.claude,
1662
+ letterSpacing: "0.2em",
1663
+ }}
1664
+ >
1665
+ 02 · CLAUDE READS
1666
+ </div>
1667
+ </div>
1668
+ </div>
1669
+
1670
+ {/* Blueprint panel — full width */}
1671
+ <div
1672
+ style={{
1673
+ position: "absolute",
1674
+ top: 430,
1675
+ left: 30,
1676
+ right: 30,
1677
+ transform: `translateY(${bpY}px)`,
1678
+ opacity: bpOp,
1679
+ }}
1680
+ >
1681
+ <div
1682
+ style={{
1683
+ borderRadius: 20,
1684
+ background: C.bg,
1685
+ border: `1px solid ${C.borderLoud}`,
1686
+ boxShadow: "0 30px 80px -15px rgba(0,0,0,0.75)",
1687
+ overflow: "hidden",
1688
+ }}
1689
+ >
1690
+ {/* Header bar */}
1691
+ <div
1692
+ style={{
1693
+ padding: "14px 20px",
1694
+ background: C.surface,
1695
+ borderBottom: `1px solid ${C.border}`,
1696
+ display: "flex",
1697
+ alignItems: "center",
1698
+ gap: 10,
1699
+ }}
1700
+ >
1701
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none">
1702
+ <path
1703
+ d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6Z"
1704
+ stroke={C.stitch}
1705
+ strokeWidth="1.8"
1706
+ strokeLinejoin="round"
1707
+ />
1708
+ <path d="M14 2v6h6" stroke={C.stitch} strokeWidth="1.8" strokeLinejoin="round" />
1709
+ </svg>
1710
+ <span
1711
+ style={{
1712
+ fontFamily: MONO,
1713
+ fontSize: 15,
1714
+ color: C.fgSoft,
1715
+ letterSpacing: "0.04em",
1716
+ }}
1717
+ >
1718
+ design-system.md
1719
+ </span>
1720
+ <span
1721
+ style={{
1722
+ marginLeft: "auto",
1723
+ fontFamily: MONO,
1724
+ fontSize: 12,
1725
+ color: C.fgDim,
1726
+ letterSpacing: "0.15em",
1727
+ }}
1728
+ >
1729
+ GENERATED BY CLAUDE
1730
+ </span>
1731
+ </div>
1732
+ {/* Content */}
1733
+ <div
1734
+ style={{
1735
+ padding: "22px 28px",
1736
+ fontFamily: MONO,
1737
+ fontSize: 19,
1738
+ lineHeight: 1.55,
1739
+ color: C.fgSoft,
1740
+ }}
1741
+ >
1742
+ <div style={{ color: C.fgDim }}>---</div>
1743
+ <BlueprintLine
1744
+ f={f}
1745
+ fps={fps}
1746
+ startSec={6.5}
1747
+ heading="# Colors"
1748
+ items={["primary: #ff4d9b", "neutral: zinc-950 → zinc-50", "accent: emerald-400"]}
1749
+ color={C.stitch}
1750
+ />
1751
+ <BlueprintLine
1752
+ f={f}
1753
+ fps={fps}
1754
+ startSec={8.5}
1755
+ heading="# Typography"
1756
+ items={['font: "Geist", sans-serif', "weights: 400, 600, 700", "tracking: -0.03em"]}
1757
+ color={C.claude}
1758
+ />
1759
+ <BlueprintLine
1760
+ f={f}
1761
+ fps={fps}
1762
+ startSec={10.4}
1763
+ heading="# Spacing"
1764
+ items={["base: 4px", "scale: 4 / 8 / 12 / 16 / 24 / 32"]}
1765
+ color={C.safe}
1766
+ />
1767
+ <BlueprintLine
1768
+ f={f}
1769
+ fps={fps}
1770
+ startSec={12.0}
1771
+ heading="# Vibe"
1772
+ items={["editorial, confident, minimal"]}
1773
+ color={C.violet}
1774
+ />
1775
+ <div style={{ color: C.fgDim, marginTop: 8 }}>---</div>
1776
+ </div>
1777
+ </div>
1778
+ </div>
1779
+
1780
+ {/* Floating "03" stage label */}
1781
+ <div
1782
+ style={{
1783
+ position: "absolute",
1784
+ top: 400,
1785
+ right: 50,
1786
+ fontFamily: MONO,
1787
+ fontSize: 14,
1788
+ color: C.fgMuted,
1789
+ letterSpacing: "0.25em",
1790
+ opacity: bpOp,
1791
+ }}
1792
+ >
1793
+ 03 · BUILDS BLUEPRINT
1794
+ </div>
1795
+ </SafeZone>
1796
+ </AbsoluteFill>
1797
+ );
1798
+ };
1799
+
1800
+ const BlueprintLine: React.FC<{
1801
+ f: number;
1802
+ fps: number;
1803
+ startSec: number;
1804
+ heading: string;
1805
+ items: string[];
1806
+ color: string;
1807
+ }> = ({ f, fps, startSec, heading, items, color }) => {
1808
+ const headS = gsapSpring(f, fps, startSec, "snappy");
1809
+ const headOp = interpolate(headS, [0, 0.4], [0, 1], {
1810
+ extrapolateLeft: "clamp",
1811
+ extrapolateRight: "clamp",
1812
+ });
1813
+ return (
1814
+ <div style={{ marginTop: 10, opacity: headOp }}>
1815
+ <span style={{ color, fontWeight: 600 }}>{heading}</span>
1816
+ {items.map((it, i) => {
1817
+ const itS = gsapSpring(f, fps, startSec + 0.2 + i * 0.12, "snappy");
1818
+ const op = interpolate(itS, [0, 0.4], [0, 1], {
1819
+ extrapolateLeft: "clamp",
1820
+ extrapolateRight: "clamp",
1821
+ });
1822
+ const y = interpolate(itS, [0, 1], [6, 0]);
1823
+ return (
1824
+ <div
1825
+ key={i}
1826
+ style={{
1827
+ marginTop: 2,
1828
+ opacity: op,
1829
+ transform: `translateY(${y}px)`,
1830
+ color: C.fgSoft,
1831
+ }}
1832
+ >
1833
+ <span style={{ color: C.fgDim }}> • </span>
1834
+ {it}
1835
+ </div>
1836
+ );
1837
+ })}
1838
+ </div>
1839
+ );
1840
+ };
1841
+
1842
+ // ═══════════════════════════════════════════════════════════════
1843
+ // SCENE 6 — CLONE (43.5 → 52s)
1844
+ // "Then it clones that look across every page."
1845
+ // ═══════════════════════════════════════════════════════════════
1846
+
1847
+ const PALETTE_CHIPS = [C.stitch, C.claude, C.safe, C.violet, C.amber, "#4285f4"];
1848
+
1849
+ const PageTile: React.FC<{
1850
+ label: string;
1851
+ idx: number;
1852
+ f: number;
1853
+ fps: number;
1854
+ baseDelay: number;
1855
+ kind: "pricing" | "faq" | "dashboard" | "onboarding";
1856
+ }> = ({ label, idx, f, fps, baseDelay, kind }) => {
1857
+ const s = gsapSpring(f, fps, baseDelay + idx * 0.14, "bouncy");
1858
+ const y = interpolate(s, [0, 1], [60, 0]);
1859
+ const sc = interpolate(s, [0, 1], [0.9, 1]);
1860
+ const o = interpolate(s, [0, 0.4], [0, 1], {
1861
+ extrapolateLeft: "clamp",
1862
+ extrapolateRight: "clamp",
1863
+ });
1864
+ return (
1865
+ <div
1866
+ style={{
1867
+ background: C.surface,
1868
+ borderRadius: 18,
1869
+ border: `1px solid ${C.border}`,
1870
+ overflow: "hidden",
1871
+ transform: `translateY(${y}px) scale(${sc})`,
1872
+ opacity: o,
1873
+ boxShadow: ds.shadow.card,
1874
+ }}
1875
+ >
1876
+ {/* Palette bar — shared across all tiles to show consistency */}
1877
+ <div
1878
+ style={{
1879
+ display: "flex",
1880
+ height: 8,
1881
+ }}
1882
+ >
1883
+ {PALETTE_CHIPS.map((c, i) => (
1884
+ <div key={i} style={{ flex: 1, background: c, opacity: 0.85 }} />
1885
+ ))}
1886
+ </div>
1887
+
1888
+ {/* Mini wireframe content per kind */}
1889
+ <div
1890
+ style={{
1891
+ padding: 14,
1892
+ height: 210,
1893
+ display: "flex",
1894
+ flexDirection: "column",
1895
+ gap: 8,
1896
+ }}
1897
+ >
1898
+ {kind === "pricing" ? (
1899
+ <>
1900
+ <div style={{ fontFamily: FONT, fontSize: 14, fontWeight: 600, color: C.fg }}>
1901
+ Pricing
1902
+ </div>
1903
+ <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 6, marginTop: 4 }}>
1904
+ {["$0", "$19", "$49"].map((p, i) => (
1905
+ <div
1906
+ key={i}
1907
+ style={{
1908
+ padding: 8,
1909
+ background: i === 1 ? C.stitchDim : C.bg,
1910
+ borderRadius: 8,
1911
+ border: `1px solid ${i === 1 ? C.stitch : C.border}`,
1912
+ textAlign: "center",
1913
+ }}
1914
+ >
1915
+ <div style={{ fontFamily: FONT, fontSize: 20, fontWeight: 700, color: i === 1 ? C.stitch : C.fg }}>
1916
+ {p}
1917
+ </div>
1918
+ <div style={{ fontFamily: MONO, fontSize: 9, color: C.fgDim, marginTop: 2 }}>
1919
+ /MO
1920
+ </div>
1921
+ </div>
1922
+ ))}
1923
+ </div>
1924
+ <div style={{ marginTop: 6, display: "flex", flexDirection: "column", gap: 4 }}>
1925
+ {[0.9, 0.7, 0.6].map((w, i) => (
1926
+ <div
1927
+ key={i}
1928
+ style={{
1929
+ height: 6,
1930
+ borderRadius: 3,
1931
+ background: C.border,
1932
+ width: `${w * 100}%`,
1933
+ }}
1934
+ />
1935
+ ))}
1936
+ </div>
1937
+ </>
1938
+ ) : kind === "faq" ? (
1939
+ <>
1940
+ <div style={{ fontFamily: FONT, fontSize: 14, fontWeight: 600, color: C.fg }}>FAQ</div>
1941
+ {[0, 1, 2, 3].map((i) => (
1942
+ <div
1943
+ key={i}
1944
+ style={{
1945
+ padding: "8px 10px",
1946
+ background: C.bg,
1947
+ borderRadius: 8,
1948
+ border: `1px solid ${C.border}`,
1949
+ display: "flex",
1950
+ justifyContent: "space-between",
1951
+ alignItems: "center",
1952
+ }}
1953
+ >
1954
+ <div
1955
+ style={{
1956
+ height: 6,
1957
+ borderRadius: 3,
1958
+ background: C.border,
1959
+ width: `${70 - i * 10}%`,
1960
+ }}
1961
+ />
1962
+ <div style={{ fontFamily: MONO, fontSize: 12, color: C.fgDim }}>+</div>
1963
+ </div>
1964
+ ))}
1965
+ </>
1966
+ ) : kind === "dashboard" ? (
1967
+ <>
1968
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
1969
+ <div style={{ fontFamily: FONT, fontSize: 14, fontWeight: 600, color: C.fg }}>
1970
+ Dashboard
1971
+ </div>
1972
+ <div
1973
+ style={{
1974
+ padding: "2px 8px",
1975
+ fontFamily: MONO,
1976
+ fontSize: 9,
1977
+ color: C.safe,
1978
+ background: C.safeDim,
1979
+ borderRadius: 6,
1980
+ letterSpacing: "0.1em",
1981
+ }}
1982
+ >
1983
+ LIVE
1984
+ </div>
1985
+ </div>
1986
+ <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 6, marginTop: 4 }}>
1987
+ {[
1988
+ { v: "47.2%", l: "MRR" },
1989
+ { v: "2.8K", l: "USERS" },
1990
+ ].map((m, i) => (
1991
+ <div
1992
+ key={i}
1993
+ style={{
1994
+ padding: 8,
1995
+ background: C.bg,
1996
+ borderRadius: 8,
1997
+ border: `1px solid ${C.border}`,
1998
+ }}
1999
+ >
2000
+ <div style={{ fontFamily: FONT, fontSize: 16, fontWeight: 700, color: C.fg }}>
2001
+ {m.v}
2002
+ </div>
2003
+ <div style={{ fontFamily: MONO, fontSize: 9, color: C.fgDim, letterSpacing: "0.14em" }}>
2004
+ {m.l}
2005
+ </div>
2006
+ </div>
2007
+ ))}
2008
+ </div>
2009
+ {/* Mini sparkline */}
2010
+ <svg width="100%" height="60" viewBox="0 0 200 60" preserveAspectRatio="none">
2011
+ <path
2012
+ d="M0 45 L25 38 L50 42 L75 28 L100 30 L125 18 L150 22 L175 10 L200 14"
2013
+ stroke={C.stitch}
2014
+ strokeWidth="2"
2015
+ fill="none"
2016
+ />
2017
+ <path
2018
+ d="M0 45 L25 38 L50 42 L75 28 L100 30 L125 18 L150 22 L175 10 L200 14 L200 60 L0 60 Z"
2019
+ fill={`${C.stitch}22`}
2020
+ />
2021
+ </svg>
2022
+ </>
2023
+ ) : (
2024
+ <>
2025
+ <div style={{ fontFamily: FONT, fontSize: 14, fontWeight: 600, color: C.fg }}>
2026
+ Onboarding
2027
+ </div>
2028
+ <div style={{ display: "flex", alignItems: "center", gap: 6, marginTop: 6 }}>
2029
+ {[0, 1, 2, 3].map((i) => (
2030
+ <React.Fragment key={i}>
2031
+ <div
2032
+ style={{
2033
+ width: 20,
2034
+ height: 20,
2035
+ borderRadius: "50%",
2036
+ background: i <= 1 ? C.stitch : C.bg,
2037
+ border: `1.5px solid ${i <= 1 ? C.stitch : C.border}`,
2038
+ display: "flex",
2039
+ alignItems: "center",
2040
+ justifyContent: "center",
2041
+ fontFamily: MONO,
2042
+ fontSize: 10,
2043
+ color: i <= 1 ? "#fff" : C.fgDim,
2044
+ fontWeight: 600,
2045
+ }}
2046
+ >
2047
+ {i + 1}
2048
+ </div>
2049
+ {i < 3 ? (
2050
+ <div
2051
+ style={{
2052
+ flex: 1,
2053
+ height: 2,
2054
+ background: i < 1 ? C.stitch : C.border,
2055
+ }}
2056
+ />
2057
+ ) : null}
2058
+ </React.Fragment>
2059
+ ))}
2060
+ </div>
2061
+ <div style={{ marginTop: 10, display: "flex", flexDirection: "column", gap: 5 }}>
2062
+ {[0.95, 0.8, 0.55, 0.4].map((w, i) => (
2063
+ <div
2064
+ key={i}
2065
+ style={{
2066
+ height: 6,
2067
+ borderRadius: 3,
2068
+ background: C.border,
2069
+ width: `${w * 100}%`,
2070
+ }}
2071
+ />
2072
+ ))}
2073
+ </div>
2074
+ <div
2075
+ style={{
2076
+ marginTop: "auto",
2077
+ padding: "6px 10px",
2078
+ borderRadius: 8,
2079
+ background: C.stitch,
2080
+ color: "#fff",
2081
+ fontFamily: FONT,
2082
+ fontSize: 11,
2083
+ fontWeight: 600,
2084
+ textAlign: "center",
2085
+ letterSpacing: "-0.01em",
2086
+ }}
2087
+ >
2088
+ Continue →
2089
+ </div>
2090
+ </>
2091
+ )}
2092
+ </div>
2093
+
2094
+ {/* Footer label */}
2095
+ <div
2096
+ style={{
2097
+ padding: "8px 14px",
2098
+ borderTop: `1px solid ${C.border}`,
2099
+ fontFamily: MONO,
2100
+ fontSize: 12,
2101
+ color: C.fgMuted,
2102
+ letterSpacing: "0.16em",
2103
+ display: "flex",
2104
+ justifyContent: "space-between",
2105
+ alignItems: "center",
2106
+ }}
2107
+ >
2108
+ <span>{label}</span>
2109
+ <span style={{ color: C.stitch }}>●</span>
2110
+ </div>
2111
+ </div>
2112
+ );
2113
+ };
2114
+
2115
+ const CloneScene: React.FC<SP> = ({ frame, fps }) => {
2116
+ const op = so(frame, fps, SEGS[5].s, SEGS[5].e);
2117
+ if (op === 0) return null;
2118
+ const f = lf(frame, fps, SEGS[5].s);
2119
+
2120
+ const titleS = gsapSpring(f, fps, 0, "snappy");
2121
+ const titleOp = interpolate(titleS, [0, 0.4], [0, 1], {
2122
+ extrapolateLeft: "clamp",
2123
+ extrapolateRight: "clamp",
2124
+ });
2125
+
2126
+ // Palette preview strip — shown at top
2127
+ return (
2128
+ <AbsoluteFill style={{ opacity: op }}>
2129
+ <SafeZone>
2130
+ {/* Title */}
2131
+ <div
2132
+ style={{
2133
+ position: "absolute",
2134
+ top: 0,
2135
+ left: 0,
2136
+ right: 0,
2137
+ textAlign: "center",
2138
+ opacity: titleOp,
2139
+ }}
2140
+ >
2141
+ <div
2142
+ style={{
2143
+ fontFamily: MONO,
2144
+ fontSize: 15,
2145
+ color: C.stitch,
2146
+ letterSpacing: "0.26em",
2147
+ marginBottom: 8,
2148
+ }}
2149
+ >
2150
+ —— CLONES ACROSS EVERY PAGE ——
2151
+ </div>
2152
+ <div
2153
+ style={{
2154
+ fontFamily: FONT,
2155
+ fontSize: 60,
2156
+ fontWeight: 700,
2157
+ color: C.fg,
2158
+ letterSpacing: "-0.038em",
2159
+ lineHeight: 1.0,
2160
+ }}
2161
+ >
2162
+ one palette.{" "}
2163
+ <span style={{ color: C.stitch }}>every surface.</span>
2164
+ </div>
2165
+ </div>
2166
+
2167
+ {/* Shared palette legend */}
2168
+ <div
2169
+ style={{
2170
+ position: "absolute",
2171
+ top: 175,
2172
+ left: "50%",
2173
+ transform: "translate(-50%, 0)",
2174
+ display: "flex",
2175
+ alignItems: "center",
2176
+ gap: 10,
2177
+ padding: "10px 18px",
2178
+ background: C.surface,
2179
+ border: `1px solid ${C.border}`,
2180
+ borderRadius: 9999,
2181
+ opacity: interpolate(f, [0.4 * fps, 1 * fps], [0, 1], {
2182
+ extrapolateLeft: "clamp",
2183
+ extrapolateRight: "clamp",
2184
+ }),
2185
+ }}
2186
+ >
2187
+ <span
2188
+ style={{
2189
+ fontFamily: MONO,
2190
+ fontSize: 12,
2191
+ color: C.fgMuted,
2192
+ letterSpacing: "0.2em",
2193
+ }}
2194
+ >
2195
+ SHARED PALETTE
2196
+ </span>
2197
+ <div style={{ display: "flex", gap: 4 }}>
2198
+ {PALETTE_CHIPS.map((c, i) => (
2199
+ <div
2200
+ key={i}
2201
+ style={{
2202
+ width: 14,
2203
+ height: 14,
2204
+ borderRadius: 4,
2205
+ background: c,
2206
+ border: "1px solid rgba(255,255,255,0.15)",
2207
+ }}
2208
+ />
2209
+ ))}
2210
+ </div>
2211
+ </div>
2212
+
2213
+ {/* 2x2 bento grid */}
2214
+ <div
2215
+ style={{
2216
+ position: "absolute",
2217
+ top: 240,
2218
+ left: 40,
2219
+ right: 40,
2220
+ display: "grid",
2221
+ gridTemplateColumns: "1fr 1fr",
2222
+ gap: 18,
2223
+ }}
2224
+ >
2225
+ <PageTile label="PRICING.TSX" idx={0} f={f} fps={fps} baseDelay={0.9} kind="pricing" />
2226
+ <PageTile label="FAQ.TSX" idx={1} f={f} fps={fps} baseDelay={0.9} kind="faq" />
2227
+ <PageTile label="DASHBOARD.TSX" idx={2} f={f} fps={fps} baseDelay={0.9} kind="dashboard" />
2228
+ <PageTile label="ONBOARDING.TSX" idx={3} f={f} fps={fps} baseDelay={0.9} kind="onboarding" />
2229
+ </div>
2230
+ </SafeZone>
2231
+ </AbsoluteFill>
2232
+ );
2233
+ };
2234
+
2235
+ // ═══════════════════════════════════════════════════════════════
2236
+ // SCENE 7 — TEST (51.5 → 67.5s)
2237
+ // "Tested full loop. 3 concepts → 8 pages. Never Figma. Never design brief."
2238
+ // ═══════════════════════════════════════════════════════════════
2239
+
2240
+ const ConceptCard: React.FC<{
2241
+ idx: number;
2242
+ f: number;
2243
+ fps: number;
2244
+ baseDelay: number;
2245
+ name: string;
2246
+ palette: string[];
2247
+ selected: boolean;
2248
+ selectT: number;
2249
+ }> = ({ idx, f, fps, baseDelay, name, palette, selected, selectT }) => {
2250
+ const s = gsapSpring(f, fps, baseDelay + idx * 0.15, "bouncy");
2251
+ const y = interpolate(s, [0, 1], [80, 0]);
2252
+ const o = interpolate(s, [0, 0.4], [0, 1], {
2253
+ extrapolateLeft: "clamp",
2254
+ extrapolateRight: "clamp",
2255
+ });
2256
+ const nonSelectFade = selected || selectT === 0 ? 1 : 0.4;
2257
+
2258
+ return (
2259
+ <div
2260
+ style={{
2261
+ background: C.surface,
2262
+ borderRadius: 20,
2263
+ border: `2px solid ${selected ? C.stitch : C.border}`,
2264
+ padding: 18,
2265
+ transform: `translateY(${y}px) scale(${selected ? 1 + selectT * 0.05 : 1})`,
2266
+ opacity: o * nonSelectFade,
2267
+ boxShadow: selected
2268
+ ? `0 30px 70px -10px rgba(255,77,155,${0.2 + selectT * 0.3}), 0 0 ${30 + selectT * 30}px ${C.stitchDim}`
2269
+ : ds.shadow.card,
2270
+ transition: "box-shadow 200ms",
2271
+ position: "relative",
2272
+ }}
2273
+ >
2274
+ {/* Selection ring */}
2275
+ {selected ? (
2276
+ <div
2277
+ style={{
2278
+ position: "absolute",
2279
+ inset: -6,
2280
+ borderRadius: 24,
2281
+ border: `2px dashed ${C.stitch}`,
2282
+ opacity: selectT,
2283
+ transform: `rotate(${(f * 0.6) % 360}deg)`,
2284
+ pointerEvents: "none",
2285
+ }}
2286
+ />
2287
+ ) : null}
2288
+
2289
+ <div
2290
+ style={{
2291
+ fontFamily: MONO,
2292
+ fontSize: 11,
2293
+ color: C.fgDim,
2294
+ letterSpacing: "0.2em",
2295
+ marginBottom: 8,
2296
+ }}
2297
+ >
2298
+ CONCEPT · 0{idx + 1}
2299
+ </div>
2300
+ <div
2301
+ style={{
2302
+ fontFamily: FONT,
2303
+ fontSize: 26,
2304
+ fontWeight: 700,
2305
+ color: C.fg,
2306
+ letterSpacing: "-0.03em",
2307
+ lineHeight: 1.05,
2308
+ marginBottom: 12,
2309
+ }}
2310
+ >
2311
+ {name}
2312
+ </div>
2313
+ {/* Palette strip */}
2314
+ <div style={{ display: "flex", gap: 4, marginBottom: 12 }}>
2315
+ {palette.map((c, i) => (
2316
+ <div
2317
+ key={i}
2318
+ style={{
2319
+ flex: 1,
2320
+ height: 36,
2321
+ borderRadius: 6,
2322
+ background: c,
2323
+ }}
2324
+ />
2325
+ ))}
2326
+ </div>
2327
+ {/* Mock type sample */}
2328
+ <div
2329
+ style={{
2330
+ fontFamily: FONT,
2331
+ fontSize: 32,
2332
+ fontWeight: 700,
2333
+ color: palette[0],
2334
+ letterSpacing: "-0.045em",
2335
+ lineHeight: 1,
2336
+ }}
2337
+ >
2338
+ Aa
2339
+ </div>
2340
+ <div
2341
+ style={{
2342
+ fontFamily: MONO,
2343
+ fontSize: 10,
2344
+ color: C.fgDim,
2345
+ letterSpacing: "0.15em",
2346
+ marginTop: 4,
2347
+ }}
2348
+ >
2349
+ {name === "Aurora" ? "GEIST · SANS" : name === "Monograph" ? "OUTFIT · SANS" : "SATOSHI · SANS"}
2350
+ </div>
2351
+
2352
+ {selected && selectT > 0.2 ? (
2353
+ <div
2354
+ style={{
2355
+ position: "absolute",
2356
+ top: -12,
2357
+ right: -12,
2358
+ width: 40,
2359
+ height: 40,
2360
+ borderRadius: "50%",
2361
+ background: C.stitch,
2362
+ display: "flex",
2363
+ alignItems: "center",
2364
+ justifyContent: "center",
2365
+ boxShadow: `0 0 20px ${C.stitch}`,
2366
+ transform: `scale(${selectT})`,
2367
+ }}
2368
+ >
2369
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none">
2370
+ <path
2371
+ d="M5 12.5 10 17 20 7"
2372
+ stroke="#fff"
2373
+ strokeWidth="3"
2374
+ strokeLinecap="round"
2375
+ strokeLinejoin="round"
2376
+ />
2377
+ </svg>
2378
+ </div>
2379
+ ) : null}
2380
+ </div>
2381
+ );
2382
+ };
2383
+
2384
+ const MiniPageCard: React.FC<{
2385
+ idx: number;
2386
+ f: number;
2387
+ fps: number;
2388
+ baseDelay: number;
2389
+ label: string;
2390
+ }> = ({ idx, f, fps, baseDelay, label }) => {
2391
+ const s = gsapSpring(f, fps, baseDelay + idx * 0.07, "bouncy");
2392
+ const y = interpolate(s, [0, 1], [40, 0]);
2393
+ const o = interpolate(s, [0, 0.4], [0, 1], {
2394
+ extrapolateLeft: "clamp",
2395
+ extrapolateRight: "clamp",
2396
+ });
2397
+ return (
2398
+ <div
2399
+ style={{
2400
+ background: C.surface,
2401
+ borderRadius: 12,
2402
+ border: `1px solid ${C.border}`,
2403
+ padding: 10,
2404
+ transform: `translateY(${y}px)`,
2405
+ opacity: o,
2406
+ overflow: "hidden",
2407
+ }}
2408
+ >
2409
+ {/* Palette bar */}
2410
+ <div style={{ display: "flex", height: 4, borderRadius: 2, overflow: "hidden", marginBottom: 8 }}>
2411
+ {PALETTE_CHIPS.slice(0, 5).map((c, i) => (
2412
+ <div key={i} style={{ flex: 1, background: c }} />
2413
+ ))}
2414
+ </div>
2415
+ {/* Mini wireframe lines */}
2416
+ <div style={{ display: "flex", flexDirection: "column", gap: 4, marginBottom: 6 }}>
2417
+ <div style={{ height: 6, background: C.fgSoft, width: "60%", borderRadius: 2 }} />
2418
+ <div style={{ height: 3, background: C.border, width: "90%", borderRadius: 1.5 }} />
2419
+ <div style={{ height: 3, background: C.border, width: "75%", borderRadius: 1.5 }} />
2420
+ </div>
2421
+ <div style={{ display: "flex", gap: 4 }}>
2422
+ <div style={{ flex: 1, height: 16, background: C.bg, borderRadius: 4, border: `1px solid ${C.border}` }} />
2423
+ <div style={{ flex: 1, height: 16, background: C.stitch, borderRadius: 4 }} />
2424
+ </div>
2425
+ <div
2426
+ style={{
2427
+ fontFamily: MONO,
2428
+ fontSize: 9,
2429
+ color: C.fgMuted,
2430
+ letterSpacing: "0.14em",
2431
+ marginTop: 6,
2432
+ textAlign: "center",
2433
+ }}
2434
+ >
2435
+ {label}
2436
+ </div>
2437
+ </div>
2438
+ );
2439
+ };
2440
+
2441
+ const TestScene: React.FC<SP> = ({ frame, fps }) => {
2442
+ const op = so(frame, fps, SEGS[6].s, SEGS[6].e);
2443
+ if (op === 0) return null;
2444
+ const f = lf(frame, fps, SEGS[6].s);
2445
+
2446
+ // Phase timing within scene (0-16s):
2447
+ // 0.0-2.0 : Header "I tested the full loop"
2448
+ // 1.8-5.0 : 3 concepts appear side by side
2449
+ // 4.5-7.0 : Selection ring on middle concept
2450
+ // 6.5-10.5: 8 page tiles waterfall
2451
+ // 10.5-13 : Strike-through "Figma" and "design brief"
2452
+ // 13-15.5 : Pipeline: idea → design → code → deploy
2453
+
2454
+ // Header
2455
+ const headS = gsapSpring(f, fps, 0, "snappy");
2456
+ const headOp = interpolate(headS, [0, 0.4], [0, 1], {
2457
+ extrapolateLeft: "clamp",
2458
+ extrapolateRight: "clamp",
2459
+ });
2460
+ const headOut = interpolate(f, [10.8 * fps, 11.8 * fps], [1, 0], {
2461
+ extrapolateLeft: "clamp",
2462
+ extrapolateRight: "clamp",
2463
+ });
2464
+
2465
+ // Concepts + selection + grid — all in one continuous phase
2466
+ const conceptsOut = interpolate(f, [10.8 * fps, 11.8 * fps], [1, 0], {
2467
+ extrapolateLeft: "clamp",
2468
+ extrapolateRight: "clamp",
2469
+ });
2470
+ const selectT = interpolate(f, [4.5 * fps, 5.4 * fps], [0, 1], {
2471
+ extrapolateLeft: "clamp",
2472
+ extrapolateRight: "clamp",
2473
+ });
2474
+
2475
+ // Strike-through phase
2476
+ const strikeIn = interpolate(f, [11.2 * fps, 11.8 * fps], [0, 1], {
2477
+ extrapolateLeft: "clamp",
2478
+ extrapolateRight: "clamp",
2479
+ });
2480
+
2481
+ return (
2482
+ <AbsoluteFill style={{ opacity: op }}>
2483
+ <SafeZone>
2484
+ {/* Top header */}
2485
+ <div
2486
+ style={{
2487
+ position: "absolute",
2488
+ top: 0,
2489
+ left: 0,
2490
+ right: 0,
2491
+ textAlign: "center",
2492
+ opacity: headOp * headOut,
2493
+ }}
2494
+ >
2495
+ <div
2496
+ style={{
2497
+ fontFamily: MONO,
2498
+ fontSize: 15,
2499
+ color: C.stitch,
2500
+ letterSpacing: "0.26em",
2501
+ marginBottom: 6,
2502
+ }}
2503
+ >
2504
+ I TESTED THE FULL LOOP
2505
+ </div>
2506
+ <div
2507
+ style={{
2508
+ fontFamily: FONT,
2509
+ fontSize: 52,
2510
+ fontWeight: 700,
2511
+ color: C.fg,
2512
+ letterSpacing: "-0.035em",
2513
+ lineHeight: 1.0,
2514
+ }}
2515
+ >
2516
+ 3 concepts → pick 1 →{" "}
2517
+ <span style={{ color: C.stitch }}>8 pages</span>
2518
+ </div>
2519
+ </div>
2520
+
2521
+ {/* Three concepts */}
2522
+ <div
2523
+ style={{
2524
+ position: "absolute",
2525
+ top: 175,
2526
+ left: 30,
2527
+ right: 30,
2528
+ display: "grid",
2529
+ gridTemplateColumns: "1fr 1fr 1fr",
2530
+ gap: 14,
2531
+ opacity: conceptsOut,
2532
+ }}
2533
+ >
2534
+ <ConceptCard
2535
+ idx={0}
2536
+ f={f}
2537
+ fps={fps}
2538
+ baseDelay={1.6}
2539
+ name="Aurora"
2540
+ palette={["#ff4d9b", "#8d54ff", "#4285f4"]}
2541
+ selected={false}
2542
+ selectT={selectT}
2543
+ />
2544
+ <ConceptCard
2545
+ idx={1}
2546
+ f={f}
2547
+ fps={fps}
2548
+ baseDelay={1.6}
2549
+ name="Monograph"
2550
+ palette={["#18181b", "#52525c", "#fcbb00"]}
2551
+ selected={true}
2552
+ selectT={selectT}
2553
+ />
2554
+ <ConceptCard
2555
+ idx={2}
2556
+ f={f}
2557
+ fps={fps}
2558
+ baseDelay={1.6}
2559
+ name="Forest"
2560
+ palette={["#1a2e1f", "#4fc46a", "#D4663A"]}
2561
+ selected={false}
2562
+ selectT={selectT}
2563
+ />
2564
+ </div>
2565
+
2566
+ {/* 8 pages grid — appears after selection */}
2567
+ <div
2568
+ style={{
2569
+ position: "absolute",
2570
+ top: 550,
2571
+ left: 30,
2572
+ right: 30,
2573
+ display: "grid",
2574
+ gridTemplateColumns: "repeat(4, 1fr)",
2575
+ gap: 10,
2576
+ opacity: conceptsOut,
2577
+ }}
2578
+ >
2579
+ {["Landing", "Pricing", "FAQ", "Blog", "Dashboard", "Settings", "Auth", "Onboard"].map(
2580
+ (label, i) => (
2581
+ <MiniPageCard key={label} idx={i} f={f} fps={fps} baseDelay={6.5} label={label} />
2582
+ ),
2583
+ )}
2584
+ </div>
2585
+
2586
+ {/* --- Strike-through + pipeline (second half of scene) --- */}
2587
+ <div
2588
+ style={{
2589
+ position: "absolute",
2590
+ top: 0,
2591
+ left: 0,
2592
+ right: 0,
2593
+ textAlign: "center",
2594
+ opacity: strikeIn,
2595
+ }}
2596
+ >
2597
+ <div
2598
+ style={{
2599
+ fontFamily: MONO,
2600
+ fontSize: 15,
2601
+ color: C.stitch,
2602
+ letterSpacing: "0.26em",
2603
+ marginBottom: 10,
2604
+ }}
2605
+ >
2606
+ WHAT I DIDN'T DO
2607
+ </div>
2608
+ {/* Strikethrough rows */}
2609
+ {[
2610
+ { t: "Figma", delay: 0 },
2611
+ { t: "a design brief", delay: 0.5 },
2612
+ ].map((r, i) => {
2613
+ const rs = interpolate(f, [(11.5 + r.delay) * fps, (12.2 + r.delay) * fps], [0, 1], {
2614
+ extrapolateLeft: "clamp",
2615
+ extrapolateRight: "clamp",
2616
+ });
2617
+ return (
2618
+ <div
2619
+ key={r.t}
2620
+ style={{
2621
+ position: "relative",
2622
+ display: "inline-block",
2623
+ marginTop: i > 0 ? 14 : 0,
2624
+ opacity: interpolate(f, [(11.4 + r.delay) * fps, (11.9 + r.delay) * fps], [0, 1], {
2625
+ extrapolateLeft: "clamp",
2626
+ extrapolateRight: "clamp",
2627
+ }),
2628
+ }}
2629
+ >
2630
+ <span
2631
+ style={{
2632
+ fontFamily: FONT,
2633
+ fontSize: 62,
2634
+ fontWeight: 700,
2635
+ color: C.fgDim,
2636
+ letterSpacing: "-0.035em",
2637
+ }}
2638
+ >
2639
+ never opened{" "}
2640
+ <span style={{ color: C.fgSoft }}>{r.t}</span>
2641
+ </span>
2642
+ <div
2643
+ style={{
2644
+ position: "absolute",
2645
+ top: "55%",
2646
+ left: 0,
2647
+ width: `${rs * 100}%`,
2648
+ height: 5,
2649
+ background: C.danger,
2650
+ borderRadius: 3,
2651
+ boxShadow: `0 0 14px ${C.danger}`,
2652
+ }}
2653
+ />
2654
+ </div>
2655
+ );
2656
+ })}
2657
+ {/* Pipeline — idea → design → code → deploy */}
2658
+ <div
2659
+ style={{
2660
+ marginTop: 50,
2661
+ display: "flex",
2662
+ justifyContent: "center",
2663
+ alignItems: "center",
2664
+ gap: 16,
2665
+ flexWrap: "wrap",
2666
+ opacity: interpolate(f, [13.2 * fps, 13.8 * fps], [0, 1], {
2667
+ extrapolateLeft: "clamp",
2668
+ extrapolateRight: "clamp",
2669
+ }),
2670
+ }}
2671
+ >
2672
+ {["idea", "design", "code", "deploy"].map((step, i) => {
2673
+ const stepS = gsapSpring(f, fps, 13.2 + i * 0.18, "bouncy");
2674
+ const y = interpolate(stepS, [0, 1], [40, 0]);
2675
+ return (
2676
+ <React.Fragment key={step}>
2677
+ <div
2678
+ style={{
2679
+ padding: "10px 22px",
2680
+ background: i === 3 ? C.stitch : C.surface,
2681
+ border: `1px solid ${i === 3 ? C.stitch : C.borderLoud}`,
2682
+ borderRadius: 9999,
2683
+ fontFamily: FONT,
2684
+ fontSize: 26,
2685
+ fontWeight: 600,
2686
+ color: i === 3 ? "#fff" : C.fg,
2687
+ letterSpacing: "-0.02em",
2688
+ transform: `translateY(${y}px)`,
2689
+ boxShadow: i === 3 ? `0 0 30px ${C.stitch}88` : "none",
2690
+ }}
2691
+ >
2692
+ {step}
2693
+ </div>
2694
+ {i < 3 ? (
2695
+ <svg width="40" height="20" viewBox="0 0 40 20">
2696
+ <path
2697
+ d="M4 10 H34 M28 4 L34 10 L28 16"
2698
+ stroke={C.fgMuted}
2699
+ strokeWidth="2.5"
2700
+ fill="none"
2701
+ strokeLinecap="round"
2702
+ strokeLinejoin="round"
2703
+ />
2704
+ </svg>
2705
+ ) : null}
2706
+ </React.Fragment>
2707
+ );
2708
+ })}
2709
+ </div>
2710
+ <div
2711
+ style={{
2712
+ marginTop: 24,
2713
+ fontFamily: MONO,
2714
+ fontSize: 18,
2715
+ color: C.fgMuted,
2716
+ letterSpacing: "0.2em",
2717
+ opacity: interpolate(f, [14.4 * fps, 15 * fps], [0, 1], {
2718
+ extrapolateLeft: "clamp",
2719
+ extrapolateRight: "clamp",
2720
+ }),
2721
+ }}
2722
+ >
2723
+ ONE · CHAT · WINDOW.
2724
+ </div>
2725
+ </div>
2726
+ </SafeZone>
2727
+ </AbsoluteFill>
2728
+ );
2729
+ };
2730
+
2731
+ // ═══════════════════════════════════════════════════════════════
2732
+ // SCENE 8 — CTA (67 → 76.56s)
2733
+ // "Solo founders got a design team. Like, follow, drop AI."
2734
+ // ═══════════════════════════════════════════════════════════════
2735
+
2736
+ const CTAScene: React.FC<SP> = ({ frame, fps }) => {
2737
+ const op = so(frame, fps, SEGS[7].s, SEGS[7].e);
2738
+ if (op === 0) return null;
2739
+ const f = lf(frame, fps, SEGS[7].s);
2740
+
2741
+ // Big closer headline
2742
+ const hS = gsapSpring(f, fps, 0.1, "bouncy");
2743
+ const hScale = interpolate(hS, [0, 1], [1.4, 1]);
2744
+ const hOp = interpolate(hS, [0, 0.35], [0, 1], {
2745
+ extrapolateLeft: "clamp",
2746
+ extrapolateRight: "clamp",
2747
+ });
2748
+
2749
+ // Setup guide prompt
2750
+ const guideOp = interpolate(f, [2.5 * fps, 3.1 * fps], [0, 1], {
2751
+ extrapolateLeft: "clamp",
2752
+ extrapolateRight: "clamp",
2753
+ });
2754
+
2755
+ // Three CTA icons
2756
+ const icons = [
2757
+ { delay: 4.3, key: "like", label: "LIKE" },
2758
+ { delay: 4.5, key: "follow", label: "FOLLOW" },
2759
+ { delay: 4.7, key: "comment", label: "COMMENT" },
2760
+ ] as const;
2761
+
2762
+ // Comment bubble typewriter for "AI"
2763
+ const aiOp = interpolate(f, [6.3 * fps, 6.8 * fps], [0, 1], {
2764
+ extrapolateLeft: "clamp",
2765
+ extrapolateRight: "clamp",
2766
+ });
2767
+ const aiPulse = 1 + Math.sin(f * 0.3) * 0.04;
2768
+
2769
+ return (
2770
+ <AbsoluteFill style={{ opacity: op }}>
2771
+ <SafeZone>
2772
+ {/* Big headline */}
2773
+ <div
2774
+ style={{
2775
+ position: "absolute",
2776
+ top: 10,
2777
+ left: 0,
2778
+ right: 0,
2779
+ textAlign: "center",
2780
+ transform: `scale(${hScale})`,
2781
+ opacity: hOp,
2782
+ }}
2783
+ >
2784
+ <div
2785
+ style={{
2786
+ fontFamily: MONO,
2787
+ fontSize: 15,
2788
+ color: C.stitch,
2789
+ letterSpacing: "0.26em",
2790
+ marginBottom: 12,
2791
+ }}
2792
+ >
2793
+ SOLO FOUNDERS
2794
+ </div>
2795
+ <div
2796
+ style={{
2797
+ fontFamily: FONT,
2798
+ fontSize: 82,
2799
+ fontWeight: 700,
2800
+ color: C.fg,
2801
+ letterSpacing: "-0.045em",
2802
+ lineHeight: 0.96,
2803
+ }}
2804
+ >
2805
+ just got a
2806
+ </div>
2807
+ <div
2808
+ style={{
2809
+ fontFamily: FONT,
2810
+ fontSize: 110,
2811
+ fontWeight: 700,
2812
+ color: C.stitch,
2813
+ letterSpacing: "-0.05em",
2814
+ lineHeight: 0.96,
2815
+ marginTop: 6,
2816
+ textShadow: `0 0 70px rgba(255,77,155,0.55)`,
2817
+ }}
2818
+ >
2819
+ design team.
2820
+ </div>
2821
+ </div>
2822
+
2823
+ {/* Want setup guide */}
2824
+ <div
2825
+ style={{
2826
+ position: "absolute",
2827
+ top: 480,
2828
+ left: 0,
2829
+ right: 0,
2830
+ textAlign: "center",
2831
+ opacity: guideOp,
2832
+ }}
2833
+ >
2834
+ <div
2835
+ style={{
2836
+ display: "inline-flex",
2837
+ alignItems: "center",
2838
+ gap: 12,
2839
+ padding: "14px 28px",
2840
+ background: C.claudeDim,
2841
+ border: `1px solid ${C.claude}`,
2842
+ borderRadius: 9999,
2843
+ fontFamily: FONT,
2844
+ fontSize: 30,
2845
+ fontWeight: 600,
2846
+ color: C.claudeSoft,
2847
+ letterSpacing: "-0.02em",
2848
+ boxShadow: `0 0 50px rgba(212,102,58,0.3)`,
2849
+ }}
2850
+ >
2851
+ <svg width="26" height="26" viewBox="0 0 24 24" fill="none">
2852
+ <path
2853
+ d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6Z"
2854
+ stroke={C.claudeSoft}
2855
+ strokeWidth="2"
2856
+ strokeLinejoin="round"
2857
+ />
2858
+ <path d="M14 2v6h6M8 13h8M8 17h6" stroke={C.claudeSoft} strokeWidth="2" strokeLinejoin="round" />
2859
+ </svg>
2860
+ want the full setup guide?
2861
+ </div>
2862
+ </div>
2863
+
2864
+ {/* CTA row */}
2865
+ <div
2866
+ style={{
2867
+ position: "absolute",
2868
+ top: 600,
2869
+ left: 0,
2870
+ right: 0,
2871
+ display: "flex",
2872
+ justifyContent: "center",
2873
+ gap: 32,
2874
+ }}
2875
+ >
2876
+ {icons.map((icn, i) => {
2877
+ const s = gsapSpring(f, fps, icn.delay, "bouncy");
2878
+ const scale = interpolate(s, [0, 1], [0, 1]);
2879
+ const pulse = 1 + Math.sin((f + i * 8) * 0.18) * 0.04;
2880
+ return (
2881
+ <div
2882
+ key={icn.key}
2883
+ style={{
2884
+ display: "flex",
2885
+ flexDirection: "column",
2886
+ alignItems: "center",
2887
+ gap: 10,
2888
+ transform: `scale(${scale * pulse})`,
2889
+ }}
2890
+ >
2891
+ <div
2892
+ style={{
2893
+ width: 108,
2894
+ height: 108,
2895
+ borderRadius: "50%",
2896
+ background: C.surface,
2897
+ border: `2px solid ${C.stitch}`,
2898
+ display: "flex",
2899
+ alignItems: "center",
2900
+ justifyContent: "center",
2901
+ boxShadow: `0 0 40px rgba(255,77,155,0.45)`,
2902
+ }}
2903
+ >
2904
+ {icn.key === "like" ? (
2905
+ <svg width="54" height="54" viewBox="0 0 24 24" fill={C.stitch}>
2906
+ <path d="M12 21s-7-4.35-7-10a4 4 0 0 1 7-2.65A4 4 0 0 1 19 11c0 5.65-7 10-7 10Z" />
2907
+ </svg>
2908
+ ) : icn.key === "follow" ? (
2909
+ <svg width="54" height="54" viewBox="0 0 24 24" fill="none">
2910
+ <circle cx="12" cy="8" r="4" stroke={C.stitch} strokeWidth="2.5" />
2911
+ <path
2912
+ d="M4 21a8 8 0 0 1 16 0"
2913
+ stroke={C.stitch}
2914
+ strokeWidth="2.5"
2915
+ strokeLinecap="round"
2916
+ />
2917
+ <path
2918
+ d="M20 3v6M17 6h6"
2919
+ stroke={C.stitch}
2920
+ strokeWidth="2.5"
2921
+ strokeLinecap="round"
2922
+ />
2923
+ </svg>
2924
+ ) : (
2925
+ <svg width="54" height="54" viewBox="0 0 24 24" fill="none">
2926
+ <path
2927
+ d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"
2928
+ stroke={C.stitch}
2929
+ strokeWidth="2.5"
2930
+ strokeLinejoin="round"
2931
+ strokeLinecap="round"
2932
+ />
2933
+ </svg>
2934
+ )}
2935
+ </div>
2936
+ <div
2937
+ style={{
2938
+ fontFamily: MONO,
2939
+ fontSize: 13,
2940
+ fontWeight: 500,
2941
+ color: C.fgMuted,
2942
+ letterSpacing: "0.22em",
2943
+ }}
2944
+ >
2945
+ {icn.label}
2946
+ </div>
2947
+ </div>
2948
+ );
2949
+ })}
2950
+ </div>
2951
+
2952
+ {/* Comment bubble with "AI" */}
2953
+ <div
2954
+ style={{
2955
+ position: "absolute",
2956
+ bottom: 20,
2957
+ left: 0,
2958
+ right: 0,
2959
+ display: "flex",
2960
+ justifyContent: "center",
2961
+ opacity: aiOp,
2962
+ transform: `scale(${aiPulse})`,
2963
+ }}
2964
+ >
2965
+ <div
2966
+ style={{
2967
+ display: "flex",
2968
+ alignItems: "center",
2969
+ gap: 14,
2970
+ padding: "14px 24px",
2971
+ background: C.stitch,
2972
+ borderRadius: 9999,
2973
+ boxShadow: `0 20px 50px -10px rgba(255,77,155,0.6), 0 0 60px rgba(255,77,155,0.4)`,
2974
+ }}
2975
+ >
2976
+ <span
2977
+ style={{
2978
+ fontFamily: FONT,
2979
+ fontSize: 22,
2980
+ fontWeight: 500,
2981
+ color: "rgba(255,255,255,0.85)",
2982
+ letterSpacing: "-0.02em",
2983
+ }}
2984
+ >
2985
+ drop
2986
+ </span>
2987
+ <span
2988
+ style={{
2989
+ fontFamily: FONT,
2990
+ fontSize: 44,
2991
+ fontWeight: 700,
2992
+ color: "#fff",
2993
+ letterSpacing: "-0.04em",
2994
+ }}
2995
+ >
2996
+ "AI"
2997
+ </span>
2998
+ <span
2999
+ style={{
3000
+ fontFamily: FONT,
3001
+ fontSize: 22,
3002
+ fontWeight: 500,
3003
+ color: "rgba(255,255,255,0.85)",
3004
+ letterSpacing: "-0.02em",
3005
+ }}
3006
+ >
3007
+ in the comments
3008
+ </span>
3009
+ </div>
3010
+ </div>
3011
+ </SafeZone>
3012
+ </AbsoluteFill>
3013
+ );
3014
+ };
3015
+
3016
+ // ═══════════════════════════════════════════════════════════════
3017
+ // MAIN COMPOSITION
3018
+ // ═══════════════════════════════════════════════════════════════
3019
+
3020
+ export const StitchReel: React.FC = () => {
3021
+ const frame = useCurrentFrame();
3022
+ const { fps } = useVideoConfig();
3023
+
3024
+ return (
3025
+ <AbsoluteFill style={{ backgroundColor: C.bg, fontFamily: FONT }}>
3026
+ {/* REFERENCE-STRIP: <Audio> tag removed — bring your own voiceover */}
3027
+
3028
+ <Background frame={frame} fps={fps} />
3029
+
3030
+ <HookScene frame={frame} fps={fps} />
3031
+ <StackScene frame={frame} fps={fps} />
3032
+ <FeaturesScene frame={frame} fps={fps} />
3033
+ <UnlockScene frame={frame} fps={fps} />
3034
+ <MagicScene frame={frame} fps={fps} />
3035
+ <CloneScene frame={frame} fps={fps} />
3036
+ <TestScene frame={frame} fps={fps} />
3037
+ <CTAScene frame={frame} fps={fps} />
3038
+ </AbsoluteFill>
3039
+ );
3040
+ };