wizz-method 2.15.0 → 2.17.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.
- package/README.md +12 -1
- package/THIRD-PARTY-NOTICES.md +6 -0
- package/package.json +5 -2
- package/skills-registry.yaml +18 -6
- package/src/bmm-skills/1-analysis/wizz-agent-analyst/customize.toml +1 -1
- package/src/bmm-skills/1-analysis/wizz-set-feature/SKILL.md +34 -12
- package/src/bmm-skills/2-plan-workflows/wizz-agent-pm/customize.toml +1 -1
- package/src/bmm-skills/2-plan-workflows/wizz-agent-ux-designer/customize.toml +1 -1
- package/src/bmm-skills/3-solutioning/wizz-agent-architect/customize.toml +1 -1
- package/src/bmm-skills/4-implementation/wizz-agent-dev/customize.toml +1 -1
- package/src/bmm-skills/module.yaml +10 -0
- package/src/core-skills/_shared/handoff-protocol.md +10 -0
- package/src/modules/wizz/_shared/encerramento.md +4 -0
- package/src/modules/wizz/_shared/organizacao.md +58 -0
- package/src/modules/wizz/_shared/video-pipeline.md +1 -0
- package/src/modules/wizz/agents/wizz-maestro/SKILL.md +9 -1
- package/src/modules/wizz/agents/wizz-maestro/customize.toml +8 -2
- package/src/skills-lib/wizz-inicio-de-projeto/SKILL.md +11 -0
- package/src/skills-lib/wizz-router/SKILL.md +3 -1
- package/src/skills-lib/wizz-router/references/routing-table-flat.md +1 -0
- package/src/skills-lib/wizz-video-styles/SKILL.md +66 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/CREDITS.md +54 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/LIBRARIES.md +31 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/kit.json +182 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/music/acoustic-lifestyle.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/music/cinematic-tense.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/music/classical-light.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/music/corporate-light.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/music/lofi-chill.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/music/meme-beat.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/music/piano-editorial.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/music/tech-pulse.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/sfx/boom-impact.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/sfx/click-mouse.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/sfx/ding-notify.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/sfx/glitch-short.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/sfx/ka-ching.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/sfx/pop-bubble.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/sfx/record-scratch.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/sfx/riser-short.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/sfx/swoosh-text.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/sfx/typing-short.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/sfx/whoosh-fast.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/audio/sfx/whoosh-soft.mp3 +0 -0
- package/src/skills-lib/wizz-video-styles/assets/remotion/editorial/EditorialReel.tsx +111 -0
- package/src/skills-lib/wizz-video-styles/assets/remotion/editorial/README.md +77 -0
- package/src/skills-lib/wizz-video-styles/assets/remotion/editorial/captions.tsx +56 -0
- package/src/skills-lib/wizz-video-styles/assets/remotion/editorial/example-words.json +285 -0
- package/src/skills-lib/wizz-video-styles/assets/remotion/editorial/primitives.tsx +241 -0
- package/src/skills-lib/wizz-video-styles/assets/remotion/editorial/scenes.tsx +113 -0
- package/src/skills-lib/wizz-video-styles/assets/remotion/editorial/theme.ts +68 -0
- package/src/skills-lib/wizz-video-styles/assets/remotion/editorial/timeline.ts +65 -0
- package/src/skills-lib/wizz-video-styles/data/recipes.json +341 -0
- package/src/skills-lib/wizz-video-styles/data/references.json +3397 -0
- package/src/skills-lib/wizz-video-styles/data/styles.json +726 -0
- package/src/skills-lib/wizz-video-styles/references/audio.md +54 -0
- package/src/skills-lib/wizz-video-styles/references/measured-patterns.md +38 -0
- package/src/skills-lib/wizz-video-styles/references/research-method.md +33 -0
- package/src/skills-lib/wizz-video-styles/references/style-picker.md +51 -0
- package/src/skills-lib/wizz-video-styles/references/styles/camera-no-setup.md +60 -0
- package/src/skills-lib/wizz-video-styles/references/styles/desafio-numero.md +60 -0
- package/src/skills-lib/wizz-video-styles/references/styles/esquete.md +57 -0
- package/src/skills-lib/wizz-video-styles/references/styles/experimento.md +56 -0
- package/src/skills-lib/wizz-video-styles/references/styles/explicador-animado.md +62 -0
- package/src/skills-lib/wizz-video-styles/references/styles/macro-asmr.md +61 -0
- package/src/skills-lib/wizz-video-styles/references/styles/meme-rapido.md +65 -0
- package/src/skills-lib/wizz-video-styles/references/styles/motion-editorial.md +69 -0
- package/src/skills-lib/wizz-video-styles/references/styles/podcast-clip.md +61 -0
- package/src/skills-lib/wizz-video-styles/references/styles/pov-vlog.md +57 -0
- package/src/skills-lib/wizz-video-styles/references/styles/quadro-branco.md +60 -0
- package/src/skills-lib/wizz-video-styles/references/styles/talking-head-cards.md +62 -0
- package/src/skills-lib/wizz-video-styles/references/styles/talking-head-legenda.md +62 -0
- package/src/skills-lib/wizz-video-styles/references/styles/tour.md +59 -0
- package/src/skills-lib/wizz-video-styles/scripts/analyze.sh +66 -0
- package/src/skills-lib/wizz-video-styles/scripts/build_catalog.py +128 -0
- package/tools/hooks/README.md +14 -2
- package/tools/hooks/session-rules.js +65 -140
- package/tools/hooks/wizz-arrumar.js +418 -0
- package/tools/hooks/wizz-artifact-guard.js +160 -0
- package/tools/hooks/wizz-feature-lib.js +414 -0
- package/tools/hooks/wizz-feature.js +401 -0
- package/tools/hooks/wizz-layout-lib.js +152 -0
- package/tools/hooks/wizz-router-enforce.js +21 -1
- package/tools/installer/core/doctor.js +5 -1
- package/tools/installer/core/hooks-settings.js +10 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
{
|
|
2
|
+
"words": [
|
|
3
|
+
{
|
|
4
|
+
"text": "I",
|
|
5
|
+
"start": 0.04,
|
|
6
|
+
"end": 0.23
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"text": "shipped",
|
|
10
|
+
"start": 0.26,
|
|
11
|
+
"end": 0.63
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"text": "a",
|
|
15
|
+
"start": 0.66,
|
|
16
|
+
"end": 0.85
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"text": "whole",
|
|
20
|
+
"start": 0.88,
|
|
21
|
+
"end": 1.19
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"text": "app",
|
|
25
|
+
"start": 1.22,
|
|
26
|
+
"end": 1.47
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"text": "in",
|
|
30
|
+
"start": 1.5,
|
|
31
|
+
"end": 1.72
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"text": "one",
|
|
35
|
+
"start": 1.75,
|
|
36
|
+
"end": 2.0
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"text": "weekend.",
|
|
40
|
+
"start": 2.03,
|
|
41
|
+
"end": 2.4
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"text": "Here",
|
|
45
|
+
"start": 2.78,
|
|
46
|
+
"end": 3.06
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"text": "is",
|
|
50
|
+
"start": 3.09,
|
|
51
|
+
"end": 3.31
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"text": "how.",
|
|
55
|
+
"start": 3.34,
|
|
56
|
+
"end": 3.59
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"text": "I",
|
|
60
|
+
"start": 3.97,
|
|
61
|
+
"end": 4.16
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"text": "only",
|
|
65
|
+
"start": 4.19,
|
|
66
|
+
"end": 4.47
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"text": "tell",
|
|
70
|
+
"start": 4.5,
|
|
71
|
+
"end": 4.78
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"text": "the",
|
|
75
|
+
"start": 4.81,
|
|
76
|
+
"end": 5.06
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"text": "agent",
|
|
80
|
+
"start": 5.09,
|
|
81
|
+
"end": 5.4
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"text": "what",
|
|
85
|
+
"start": 5.43,
|
|
86
|
+
"end": 5.71
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"text": "I",
|
|
90
|
+
"start": 5.74,
|
|
91
|
+
"end": 5.93
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"text": "want.",
|
|
95
|
+
"start": 5.96,
|
|
96
|
+
"end": 6.24
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"text": "Look",
|
|
100
|
+
"start": 6.62,
|
|
101
|
+
"end": 6.9
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"text": "at",
|
|
105
|
+
"start": 6.93,
|
|
106
|
+
"end": 7.15
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"text": "the",
|
|
110
|
+
"start": 7.18,
|
|
111
|
+
"end": 7.43
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"text": "result.",
|
|
115
|
+
"start": 7.46,
|
|
116
|
+
"end": 7.8
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"text": "A",
|
|
120
|
+
"start": 8.18,
|
|
121
|
+
"end": 8.37
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"text": "real",
|
|
125
|
+
"start": 8.4,
|
|
126
|
+
"end": 8.68
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"text": "screen.",
|
|
130
|
+
"start": 8.71,
|
|
131
|
+
"end": 9.05
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"text": "In",
|
|
135
|
+
"start": 9.43,
|
|
136
|
+
"end": 9.65
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"text": "forty",
|
|
140
|
+
"start": 9.68,
|
|
141
|
+
"end": 9.99
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"text": "eight",
|
|
145
|
+
"start": 10.02,
|
|
146
|
+
"end": 10.33
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"text": "hours,",
|
|
150
|
+
"start": 10.36,
|
|
151
|
+
"end": 10.67
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"text": "31",
|
|
155
|
+
"start": 10.85,
|
|
156
|
+
"end": 11.07
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"text": "commits",
|
|
160
|
+
"start": 11.1,
|
|
161
|
+
"end": 11.47
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"text": "and",
|
|
165
|
+
"start": 11.5,
|
|
166
|
+
"end": 11.75
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"text": "12",
|
|
170
|
+
"start": 11.78,
|
|
171
|
+
"end": 12.0
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"text": "tests.",
|
|
175
|
+
"start": 12.03,
|
|
176
|
+
"end": 12.34
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"text": "Three",
|
|
180
|
+
"start": 12.72,
|
|
181
|
+
"end": 13.03
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"text": "agents",
|
|
185
|
+
"start": 13.06,
|
|
186
|
+
"end": 13.4
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"text": "write,",
|
|
190
|
+
"start": 13.43,
|
|
191
|
+
"end": 13.74
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"text": "test",
|
|
195
|
+
"start": 13.92,
|
|
196
|
+
"end": 14.2
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"text": "and",
|
|
200
|
+
"start": 14.23,
|
|
201
|
+
"end": 14.48
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"text": "review.",
|
|
205
|
+
"start": 14.51,
|
|
206
|
+
"end": 14.85
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"text": "First",
|
|
210
|
+
"start": 15.23,
|
|
211
|
+
"end": 15.54
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"text": "a",
|
|
215
|
+
"start": 15.57,
|
|
216
|
+
"end": 15.76
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"text": "spec,",
|
|
220
|
+
"start": 15.79,
|
|
221
|
+
"end": 16.07
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"text": "then",
|
|
225
|
+
"start": 16.25,
|
|
226
|
+
"end": 16.53
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"text": "the",
|
|
230
|
+
"start": 16.56,
|
|
231
|
+
"end": 16.81
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"text": "code.",
|
|
235
|
+
"start": 16.84,
|
|
236
|
+
"end": 17.12
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"text": "Comment",
|
|
240
|
+
"start": 17.5,
|
|
241
|
+
"end": 17.87
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"text": "SHIP",
|
|
245
|
+
"start": 17.9,
|
|
246
|
+
"end": 18.18
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"text": "and",
|
|
250
|
+
"start": 18.21,
|
|
251
|
+
"end": 18.46
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
"text": "I",
|
|
255
|
+
"start": 18.49,
|
|
256
|
+
"end": 18.68
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
"text": "will",
|
|
260
|
+
"start": 18.71,
|
|
261
|
+
"end": 18.99
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"text": "send",
|
|
265
|
+
"start": 19.02,
|
|
266
|
+
"end": 19.3
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
"text": "you",
|
|
270
|
+
"start": 19.33,
|
|
271
|
+
"end": 19.58
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
"text": "the",
|
|
275
|
+
"start": 19.61,
|
|
276
|
+
"end": 19.86
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"text": "prompt.",
|
|
280
|
+
"start": 19.89,
|
|
281
|
+
"end": 20.23
|
|
282
|
+
}
|
|
283
|
+
],
|
|
284
|
+
"total": 20.33
|
|
285
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import React, { useId } from "react";
|
|
2
|
+
import { AbsoluteFill, Easing, Img, interpolate, spring, staticFile, useCurrentFrame } from "remotion";
|
|
3
|
+
import { FPS, H, W, useTheme } from "./theme";
|
|
4
|
+
|
|
5
|
+
// Vocabulário do estilo: fundo claro com grade, cards escuros com sombra funda,
|
|
6
|
+
// serifa itálica bicolor, pílula digitada, conector tracejado, raios, contador.
|
|
7
|
+
|
|
8
|
+
export const clamp = { extrapolateLeft: "clamp", extrapolateRight: "clamp" } as const;
|
|
9
|
+
export const ease = Easing.bezier(0.22, 1, 0.36, 1);
|
|
10
|
+
export const useSeconds = (): number => useCurrentFrame() / FPS;
|
|
11
|
+
export const pop = (frame: number, delay = 0): number =>
|
|
12
|
+
spring({ frame: frame - Math.round(delay * FPS), fps: FPS, config: { damping: 15, mass: 0.7 } });
|
|
13
|
+
|
|
14
|
+
const CX = W / 2;
|
|
15
|
+
|
|
16
|
+
// Envelope de cena: entra subindo com mola, sai subindo e sumindo (sem corte seco).
|
|
17
|
+
export const Stage: React.FC<{ dur: number; children: React.ReactNode }> = ({ dur, children }) => {
|
|
18
|
+
const frame = useCurrentFrame();
|
|
19
|
+
const t = frame / FPS;
|
|
20
|
+
const s = pop(frame);
|
|
21
|
+
const out = interpolate(t, [dur - 0.3, dur], [0, 1], { ...clamp, easing: Easing.in(Easing.cubic) });
|
|
22
|
+
const cam = interpolate(t, [0, dur], [1, 1.035], clamp);
|
|
23
|
+
return (
|
|
24
|
+
<AbsoluteFill
|
|
25
|
+
style={{
|
|
26
|
+
opacity: Math.min(1, s * 1.4) * (1 - out),
|
|
27
|
+
transform: `translateY(${(1 - s) * 70 - out * 60}px) scale(${(0.94 + 0.06 * s) * cam})`,
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</AbsoluteFill>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const Backdrop: React.FC = () => {
|
|
36
|
+
const c = useTheme();
|
|
37
|
+
return (
|
|
38
|
+
<AbsoluteFill
|
|
39
|
+
style={{
|
|
40
|
+
background: c.bg,
|
|
41
|
+
backgroundImage: `linear-gradient(${c.grid} 2px, transparent 2px), linear-gradient(90deg, ${c.grid} 2px, transparent 2px)`,
|
|
42
|
+
backgroundSize: "72px 72px",
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
<AbsoluteFill style={{ background: `radial-gradient(ellipse at 50% 45%, ${c.glow}, rgba(0,0,0,0) 60%, ${c.vignette})` }} />
|
|
46
|
+
</AbsoluteFill>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Título serifado em duas linhas: a primeira na cor do texto, a segunda no destaque.
|
|
51
|
+
export const Headline: React.FC<{ a: string; b: string; top?: number; delay?: number; size?: number }> = ({
|
|
52
|
+
a, b, top = 300, delay = 0, size = 104,
|
|
53
|
+
}) => {
|
|
54
|
+
const c = useTheme();
|
|
55
|
+
const frame = useCurrentFrame();
|
|
56
|
+
const s1 = pop(frame, delay);
|
|
57
|
+
const s2 = pop(frame, delay + 0.18);
|
|
58
|
+
const base = { fontFamily: c.serif, fontWeight: 700, fontSize: size, lineHeight: 1.0, letterSpacing: -2 } as const;
|
|
59
|
+
return (
|
|
60
|
+
<div style={{ position: "absolute", top, left: 0, right: 0, textAlign: "center" }}>
|
|
61
|
+
<div style={{ ...base, color: c.ink, opacity: s1, transform: `translateY(${(1 - s1) * 30}px)` }}>{a}</div>
|
|
62
|
+
<div style={{ ...base, color: c.accent, opacity: s2, transform: `translateY(${(1 - s2) * 30}px)` }}>{b}</div>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Raios que "acendem" abaixo de uma pílula (o agente trabalhando). grow: 0..1.
|
|
68
|
+
export const Rays: React.FC<{ grow: number; y: number; size?: number }> = ({ grow, y, size = 300 }) => {
|
|
69
|
+
const c = useTheme();
|
|
70
|
+
const frame = useCurrentFrame();
|
|
71
|
+
const n = 11;
|
|
72
|
+
return (
|
|
73
|
+
<div style={{ position: "absolute", left: CX - size, top: y, width: size * 2, height: size, overflow: "hidden" }}>
|
|
74
|
+
{Array.from({ length: n }, (_, i) => {
|
|
75
|
+
const angle = -80 + (160 / (n - 1)) * i;
|
|
76
|
+
const wobble = Math.sin(frame / 9 + i) * 0.06;
|
|
77
|
+
const len = size * (0.62 + (0.3 * ((i * 7) % 5)) / 4) * grow * (1 + wobble);
|
|
78
|
+
return (
|
|
79
|
+
<div
|
|
80
|
+
key={i}
|
|
81
|
+
style={{
|
|
82
|
+
position: "absolute", left: size - 17, top: 0, width: 34, height: len, borderRadius: 17,
|
|
83
|
+
background: `linear-gradient(${c.accentWarm}, ${c.accentLight})`,
|
|
84
|
+
transformOrigin: "50% 0%", transform: `rotate(${angle}deg)`,
|
|
85
|
+
boxShadow: `0 10px 18px ${c.accentShadow}`,
|
|
86
|
+
}}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
})}
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Anel girando: objeto-símbolo de abertura.
|
|
95
|
+
export const Ring: React.FC<{ y?: number; size?: number }> = ({ y = 760, size = 420 }) => {
|
|
96
|
+
const c = useTheme();
|
|
97
|
+
const frame = useCurrentFrame();
|
|
98
|
+
const s = pop(frame, 0.1);
|
|
99
|
+
return (
|
|
100
|
+
<div style={{ position: "absolute", left: CX - size / 2, top: y, width: size, height: size, transform: `scale(${s}) rotate(${frame * 0.8}deg)` }}>
|
|
101
|
+
<div style={{ position: "absolute", inset: 0, borderRadius: "50%", border: `${size * 0.186}px solid ${c.accent}`, borderRightColor: "transparent", boxShadow: `0 50px 80px ${c.accentShadow}` }} />
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Pílula escura com texto digitado (input de prompt). Tempos em segundos da cena.
|
|
107
|
+
export const Pill: React.FC<{
|
|
108
|
+
text: string; typeFrom: number; typeDur: number; y: number; w?: number; icon?: string; serif?: boolean;
|
|
109
|
+
}> = ({ text, typeFrom, typeDur, y, w = 820, icon = "✦", serif = true }) => {
|
|
110
|
+
const c = useTheme();
|
|
111
|
+
const t = useSeconds();
|
|
112
|
+
const n = Math.round(interpolate(t, [typeFrom, typeFrom + typeDur], [0, text.length], clamp));
|
|
113
|
+
const caret = n < text.length && Math.floor(t * 3) % 2 === 0;
|
|
114
|
+
return (
|
|
115
|
+
<div style={{ position: "absolute", left: CX - w / 2, top: y, width: w, height: 150, borderRadius: 40, background: `linear-gradient(${c.cardHi}, ${c.card})`, boxShadow: c.shadow, display: "flex", alignItems: "center", padding: "0 44px", gap: 28 }}>
|
|
116
|
+
<div style={{ width: 62, height: 62, borderRadius: 18, background: c.accent, color: c.onAccent, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 34 }}>{icon}</div>
|
|
117
|
+
<div style={{ fontFamily: serif ? c.serif : c.sans, fontWeight: 600, fontSize: 56, color: c.line, whiteSpace: "nowrap" }}>
|
|
118
|
+
{text.slice(0, n)}
|
|
119
|
+
<span style={{ color: c.accentLight, opacity: caret ? 1 : 0 }}>|</span>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Chip pequeno, centrado em (x, y). Entra com mola em `delay` (s).
|
|
126
|
+
export const Chip: React.FC<{
|
|
127
|
+
label: string; x: number; y: number; delay: number; accent?: boolean; big?: boolean; icon?: string;
|
|
128
|
+
}> = ({ label, x, y, delay, accent, big, icon = "▤" }) => {
|
|
129
|
+
const c = useTheme();
|
|
130
|
+
const s = pop(useCurrentFrame(), delay);
|
|
131
|
+
return (
|
|
132
|
+
<div style={{ position: "absolute", left: x, top: y, transform: `translate(-50%,-50%) scale(${s})`, opacity: s > 0.02 ? 1 : 0, background: accent ? c.accent : `linear-gradient(${c.cardHi}, ${c.card})`, color: accent ? c.onAccent : c.line, borderRadius: 26, padding: big ? "26px 40px" : "20px 32px", boxShadow: c.shadow, display: "flex", alignItems: "center", gap: 18, fontFamily: c.serif, fontWeight: 600, fontSize: big ? 52 : 44, whiteSpace: "nowrap" }}>
|
|
133
|
+
{icon ? <span style={{ fontFamily: c.sans, fontSize: big ? 34 : 28, opacity: 0.8 }}>{icon}</span> : null}
|
|
134
|
+
{label}
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Conector tracejado que se desenha de (x1,y1) a (x2,y2) a partir de `from` (s).
|
|
140
|
+
export const Dash: React.FC<{ x1: number; y1: number; x2: number; y2: number; from: number; dur?: number }> = ({
|
|
141
|
+
x1, y1, x2, y2, from, dur = 0.45,
|
|
142
|
+
}) => {
|
|
143
|
+
const c = useTheme();
|
|
144
|
+
const t = useSeconds();
|
|
145
|
+
const id = `dash${useId().replace(/[^a-zA-Z0-9]/g, "")}`;
|
|
146
|
+
const p = interpolate(t, [from, from + dur], [0, 1], { ...clamp, easing: ease });
|
|
147
|
+
const len = Math.hypot(x2 - x1, y2 - y1);
|
|
148
|
+
const d = `M ${x1} ${y1} C ${x1} ${(y1 + y2) / 2}, ${(x1 + x2) / 2} ${y2}, ${x2} ${y2}`;
|
|
149
|
+
return (
|
|
150
|
+
<svg style={{ position: "absolute", inset: 0 }} width={W} height={H}>
|
|
151
|
+
<defs>
|
|
152
|
+
<mask id={id}>
|
|
153
|
+
<path d={d} stroke="#fff" strokeWidth={10} fill="none" strokeDasharray={len * 1.5} strokeDashoffset={len * 1.5 * (1 - p)} />
|
|
154
|
+
</mask>
|
|
155
|
+
</defs>
|
|
156
|
+
<path d={d} stroke={c.dash} strokeWidth={4} fill="none" strokeDasharray="10 12" mask={`url(#${id})`} />
|
|
157
|
+
</svg>
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const resolveSrc = (src: string): string => (/^(https?:|data:)/.test(src) ? src : staticFile(src));
|
|
162
|
+
|
|
163
|
+
// Celular-card com screenshot em proporção real. `ratio` = altura/largura da imagem
|
|
164
|
+
// (iPhone 1206x2622 = 2.174). Sem `src`, desenha uma tela de exemplo (o molde roda sem assets).
|
|
165
|
+
export const PhoneCard: React.FC<{
|
|
166
|
+
src?: string; y?: number; scale?: number; focusY?: number; maxH?: number; ratio?: number;
|
|
167
|
+
}> = ({ src, y = 250, scale = 1, focusY = 0, maxH = 1180, ratio = 2622 / 1206 }) => {
|
|
168
|
+
const c = useTheme();
|
|
169
|
+
const w = 600 * scale;
|
|
170
|
+
const h = w * ratio;
|
|
171
|
+
const viewH = Math.min(h, maxH);
|
|
172
|
+
return (
|
|
173
|
+
<div style={{ position: "absolute", left: CX - w / 2 - 16, top: y, width: w + 32, height: viewH + 32, borderRadius: 74 * scale, background: c.card, padding: 16, boxShadow: c.shadow }}>
|
|
174
|
+
<div style={{ width: w, height: viewH, borderRadius: 60 * scale, overflow: "hidden", background: c.bg }}>
|
|
175
|
+
{src ? (
|
|
176
|
+
<Img src={resolveSrc(src)} style={{ width: w, height: h, transform: `translateY(${-focusY * scale}px)` }} />
|
|
177
|
+
) : (
|
|
178
|
+
<div style={{ padding: `${60 * scale}px ${40 * scale}px`, display: "flex", flexDirection: "column", gap: 28 * scale }}>
|
|
179
|
+
<div style={{ height: 70 * scale, borderRadius: 20 * scale, background: c.accent }} />
|
|
180
|
+
{Array.from({ length: 6 }, (_, i) => (
|
|
181
|
+
<div key={i} style={{ height: 120 * scale, borderRadius: 24 * scale, background: c.line, opacity: 1 - i * 0.1 }} />
|
|
182
|
+
))}
|
|
183
|
+
</div>
|
|
184
|
+
)}
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Card com número que sobe até `to` a partir de `delay` (s).
|
|
191
|
+
export const Counter: React.FC<{ to: number; label: string; y: number; delay: number }> = ({ to, label, y, delay }) => {
|
|
192
|
+
const c = useTheme();
|
|
193
|
+
const frame = useCurrentFrame();
|
|
194
|
+
const s = pop(frame, delay);
|
|
195
|
+
const v = Math.round(interpolate(frame / FPS, [delay, delay + 1.0], [0, to], { ...clamp, easing: Easing.out(Easing.cubic) }));
|
|
196
|
+
return (
|
|
197
|
+
<div style={{ position: "absolute", left: 140, right: 140, top: y, height: 330, borderRadius: 44, background: `linear-gradient(${c.cardHi}, ${c.card})`, boxShadow: c.shadow, transform: `scale(${s})`, opacity: s > 0.02 ? 1 : 0, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center" }}>
|
|
198
|
+
<div style={{ fontFamily: c.serif, fontWeight: 700, fontSize: 180, color: c.accentLight, lineHeight: 1 }}>{v}</div>
|
|
199
|
+
<div style={{ fontFamily: c.serif, fontWeight: 600, fontSize: 52, color: c.line, marginTop: 6 }}>{label}</div>
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
export type DocPage = { lines: string[]; at: number; mono?: boolean }; // `at` em s da cena
|
|
205
|
+
const REVIEW_SPOTS = [
|
|
206
|
+
{ x: 175, y: 560 }, { x: 905, y: 700 }, { x: 175, y: 1020 }, { x: 905, y: 1160 },
|
|
207
|
+
] as const;
|
|
208
|
+
|
|
209
|
+
// Card alto escuro (documento). Cada página troca o conteúdo e digita as linhas;
|
|
210
|
+
// `reviewers` pendura até 4 chips conectados por tracejado ao redor do card.
|
|
211
|
+
export const DocCard: React.FC<{
|
|
212
|
+
pages: DocPage[]; title: string; altTitle?: string; reviewers?: { labels: string[]; at: number };
|
|
213
|
+
}> = ({ pages, title, altTitle, reviewers }) => {
|
|
214
|
+
const c = useTheme();
|
|
215
|
+
const t = useSeconds();
|
|
216
|
+
const page = [...pages].reverse().find((p) => t >= p.at) ?? pages[0];
|
|
217
|
+
const isAlt = page !== pages[0];
|
|
218
|
+
const shown = page.lines.slice(0, Math.round(interpolate(t, [page.at + 0.2, page.at + 1.2], [0, page.lines.length], clamp)));
|
|
219
|
+
return (
|
|
220
|
+
<>
|
|
221
|
+
<div style={{ position: "absolute", left: 220, top: 380, width: 640, height: 980, borderRadius: 48, background: `linear-gradient(${c.cardHi}, ${c.card})`, boxShadow: c.shadow, padding: "130px 48px 0", overflow: "hidden" }}>
|
|
222
|
+
{shown.map((l, i) => (
|
|
223
|
+
<div key={`${page.at}-${i}`} style={{ fontFamily: page.mono ? c.mono : c.sans, fontSize: 25, lineHeight: page.mono ? "46px" : "42px", color: i === 0 && !page.mono ? c.accentLight : c.line, whiteSpace: "nowrap", overflow: "hidden", opacity: 0.92 }}>
|
|
224
|
+
{l}
|
|
225
|
+
</div>
|
|
226
|
+
))}
|
|
227
|
+
</div>
|
|
228
|
+
<Chip label={isAlt && altTitle ? altTitle : title} x={CX} y={380} delay={0.05} accent={isAlt} />
|
|
229
|
+
{(reviewers?.labels ?? []).slice(0, 4).map((label, i) => {
|
|
230
|
+
const spot = REVIEW_SPOTS[i];
|
|
231
|
+
const from = (reviewers?.at ?? 0) + i * 0.22;
|
|
232
|
+
return (
|
|
233
|
+
<React.Fragment key={label}>
|
|
234
|
+
<Dash x1={spot.x < CX ? 230 : 850} y1={spot.y} x2={spot.x} y2={spot.y} from={from} dur={0.2} />
|
|
235
|
+
<Chip label={label} x={spot.x} y={spot.y} delay={from} accent />
|
|
236
|
+
</React.Fragment>
|
|
237
|
+
);
|
|
238
|
+
})}
|
|
239
|
+
</>
|
|
240
|
+
);
|
|
241
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { interpolate } from "remotion";
|
|
3
|
+
import {
|
|
4
|
+
Chip, Counter, Dash, DocCard, Headline, PhoneCard, Pill, Rays, Ring, clamp, ease, useSeconds,
|
|
5
|
+
type DocPage,
|
|
6
|
+
} from "./primitives";
|
|
7
|
+
import { useTheme } from "./theme";
|
|
8
|
+
import type { Anchored } from "./timeline";
|
|
9
|
+
|
|
10
|
+
// Cenas como DADOS: cada objeto do array vira um componente. `at` é a palavra-âncora
|
|
11
|
+
// onde a cena começa; `marks` nomeia palavras internas (ex.: quando o contador sobe).
|
|
12
|
+
|
|
13
|
+
export type SceneDef = Anchored &
|
|
14
|
+
(
|
|
15
|
+
| { kind: "hook"; a: string; b: string }
|
|
16
|
+
| { kind: "prompt"; a: string; b: string; prompt: string; typedMark?: string }
|
|
17
|
+
| { kind: "phone"; src?: string; ratio?: number; scale?: number; y?: number; focusY?: number }
|
|
18
|
+
| { kind: "numbers"; chip: string; counters: { to: number; label: string; mark: string }[] }
|
|
19
|
+
| { kind: "steps"; prompt: string; items: [string, string, string]; typedMark?: string }
|
|
20
|
+
| { kind: "doc"; title: string; altTitle?: string; pages: (Omit<DocPage, "at"> & { mark?: string })[]; reviewers?: { labels: string[]; mark: string } }
|
|
21
|
+
| { kind: "cta"; a: string; b: string; keyword: string; footer: string; hideCaptions?: boolean }
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export type SceneCtx = { dur: number; mark: (name: string | undefined, fallback?: number) => number };
|
|
25
|
+
|
|
26
|
+
const Hook: React.FC<{ a: string; b: string }> = ({ a, b }) => (
|
|
27
|
+
<>
|
|
28
|
+
<Headline a={a} b={b} />
|
|
29
|
+
<Ring />
|
|
30
|
+
</>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const Prompt: React.FC<{ a: string; b: string; prompt: string; typed: number; dur: number }> = ({ a, b, prompt, typed, dur }) => {
|
|
34
|
+
const t = useSeconds();
|
|
35
|
+
return (
|
|
36
|
+
<>
|
|
37
|
+
<Headline a={a} b={b} />
|
|
38
|
+
<Pill text={prompt} typeFrom={0.2} typeDur={Math.min(typed, dur * 0.6)} y={820} />
|
|
39
|
+
<Rays grow={interpolate(t, [0.4, 1.0], [0, 1], { ...clamp, easing: ease })} y={970} />
|
|
40
|
+
</>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const Numbers: React.FC<{ chip: string; counters: { to: number; label: string; delay: number }[] }> = ({ chip, counters }) => (
|
|
45
|
+
<>
|
|
46
|
+
<Chip label={chip} x={540} y={330} delay={0.05} accent />
|
|
47
|
+
{counters.slice(0, 2).map((c, i) => (
|
|
48
|
+
<Counter key={c.label} to={c.to} label={c.label} y={470 + i * 390} delay={c.delay} />
|
|
49
|
+
))}
|
|
50
|
+
</>
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const Steps: React.FC<{ prompt: string; items: [string, string, string]; typed: number }> = ({ prompt, items, typed }) => {
|
|
54
|
+
const t = useSeconds();
|
|
55
|
+
const spots = [
|
|
56
|
+
{ x: 230, y: 1000, from: [300, 760] as const, delay: 0.25 },
|
|
57
|
+
{ x: 540, y: 1100, from: [540, 760] as const, delay: 0.55 },
|
|
58
|
+
{ x: 850, y: 1000, from: [780, 760] as const, delay: 0.85 },
|
|
59
|
+
];
|
|
60
|
+
return (
|
|
61
|
+
<>
|
|
62
|
+
<Pill text={prompt} typeFrom={0.1} typeDur={Math.max(0.6, typed - 0.1)} y={330} w={700} />
|
|
63
|
+
<Rays grow={interpolate(t, [0.1, 0.6], [0, 1], { ...clamp, easing: ease })} y={480} size={260} />
|
|
64
|
+
{spots.map((s, i) => (
|
|
65
|
+
<React.Fragment key={items[i]}>
|
|
66
|
+
<Dash x1={s.from[0]} y1={s.from[1]} x2={s.x} y2={s.y - 40} from={s.delay - 0.05} />
|
|
67
|
+
<Chip label={items[i]} x={s.x} y={s.y} delay={s.delay} />
|
|
68
|
+
</React.Fragment>
|
|
69
|
+
))}
|
|
70
|
+
</>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const Cta: React.FC<{ a: string; b: string; keyword: string; footer: string }> = ({ a, b, keyword, footer }) => {
|
|
75
|
+
const c = useTheme();
|
|
76
|
+
const t = useSeconds();
|
|
77
|
+
return (
|
|
78
|
+
<>
|
|
79
|
+
<Headline a={a} b={b} top={360} />
|
|
80
|
+
<Pill text={keyword} typeFrom={0.35} typeDur={0.5} y={760} w={520} />
|
|
81
|
+
<Rays grow={interpolate(t, [0.6, 1.1], [0, 1], { ...clamp, easing: ease })} y={910} />
|
|
82
|
+
<div style={{ position: "absolute", top: 1290, left: 120, right: 120, textAlign: "center", fontFamily: c.serif, fontWeight: 600, fontSize: 54, color: c.ink, lineHeight: 1.2 }}>
|
|
83
|
+
{footer}
|
|
84
|
+
</div>
|
|
85
|
+
</>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const SceneView: React.FC<{ scene: SceneDef; ctx: SceneCtx }> = ({ scene, ctx }) => {
|
|
90
|
+
switch (scene.kind) {
|
|
91
|
+
case "hook":
|
|
92
|
+
return <Hook a={scene.a} b={scene.b} />;
|
|
93
|
+
case "prompt":
|
|
94
|
+
return <Prompt a={scene.a} b={scene.b} prompt={scene.prompt} typed={ctx.mark(scene.typedMark, 1.1)} dur={ctx.dur} />;
|
|
95
|
+
case "phone":
|
|
96
|
+
return <PhoneCard src={scene.src} ratio={scene.ratio} scale={scene.scale ?? 1} y={scene.y ?? 260} focusY={scene.focusY} maxH={1200} />;
|
|
97
|
+
case "numbers":
|
|
98
|
+
return <Numbers chip={scene.chip} counters={scene.counters.map((c) => ({ ...c, delay: ctx.mark(c.mark, 0.3) }))} />;
|
|
99
|
+
case "steps":
|
|
100
|
+
return <Steps prompt={scene.prompt} items={scene.items} typed={ctx.mark(scene.typedMark, 0.8)} />;
|
|
101
|
+
case "doc":
|
|
102
|
+
return (
|
|
103
|
+
<DocCard
|
|
104
|
+
title={scene.title}
|
|
105
|
+
altTitle={scene.altTitle}
|
|
106
|
+
pages={scene.pages.map((p, i) => ({ lines: p.lines, mono: p.mono, at: i === 0 ? 0.2 : ctx.mark(p.mark, 1) }))}
|
|
107
|
+
reviewers={scene.reviewers ? { labels: scene.reviewers.labels, at: ctx.mark(scene.reviewers.mark, 2) } : undefined}
|
|
108
|
+
/>
|
|
109
|
+
);
|
|
110
|
+
case "cta":
|
|
111
|
+
return <Cta a={scene.a} b={scene.b} keyword={scene.keyword} footer={scene.footer} />;
|
|
112
|
+
}
|
|
113
|
+
};
|