wizz-method 2.16.0 → 2.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +6 -0
- package/package.json +1 -1
- package/skills-registry.yaml +18 -6
- package/src/modules/wizz/_shared/model-ladder.md +7 -5
- package/src/modules/wizz/_shared/video-pipeline.md +1 -0
- package/src/modules/wizz/subagents/codex/wizz-exec-mechanical.toml +2 -2
- package/src/modules/wizz/subagents/codex/wizz-exec-review.toml +2 -2
- package/src/modules/wizz/subagents/codex/wizz-exec-standard.toml +2 -2
- package/src/modules/wizz/subagents/codex/wizz-exec-strong.toml +2 -2
- package/src/modules/wizz/subagents/spec.yaml +7 -4
- 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/installer/core/project-instructions.js +2 -1
- package/tools/installer/core/subagent-models.js +112 -0
- package/tools/installer/ide/_config-driven.js +8 -3
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { AbsoluteFill, Audio, Sequence, interpolate, staticFile } from "remotion";
|
|
3
|
+
import { Captions } from "./captions";
|
|
4
|
+
import { Backdrop, Stage, clamp } from "./primitives";
|
|
5
|
+
import { SceneView, type SceneDef } from "./scenes";
|
|
6
|
+
import { ThemeContext, mergeTheme, type EditorialTheme } from "./theme";
|
|
7
|
+
import { at, buildScenes, toFrames, totalSeconds, type WordsData } from "./timeline";
|
|
8
|
+
import exampleWords from "./example-words.json";
|
|
9
|
+
|
|
10
|
+
// Composição genérica do estilo "motion editorial": um objeto por vez no centro,
|
|
11
|
+
// troca por transformação (cena sobe enquanto a próxima entra), música baixa contínua,
|
|
12
|
+
// SFX raro, legenda pequena. Tudo que muda de vídeo para vídeo entra por props.
|
|
13
|
+
|
|
14
|
+
export type SfxDef = { atWord: string; offset?: number; src: string; volume?: number; durationSec?: number };
|
|
15
|
+
|
|
16
|
+
export type AudioProps = {
|
|
17
|
+
voice?: string; // staticFile, ex.: "editorial/voice.wav"
|
|
18
|
+
music?: string; // cama contínua
|
|
19
|
+
musicVolume?: number; // padrão 0.2
|
|
20
|
+
sfx?: SfxDef[]; // em ordem de aparição na fala; no máximo ~1 a cada 10 s
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type EditorialReelProps = {
|
|
24
|
+
words: WordsData;
|
|
25
|
+
scenes: SceneDef[];
|
|
26
|
+
theme?: Partial<EditorialTheme>;
|
|
27
|
+
audio?: AudioProps;
|
|
28
|
+
overlap?: number; // s de sobreposição entre cenas
|
|
29
|
+
tailSeconds?: number; // respiro depois da última palavra
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const exampleScenes: SceneDef[] = [
|
|
33
|
+
{ kind: "hook", at: "I", a: "I shipped an app", b: "in one weekend." },
|
|
34
|
+
{ kind: "prompt", at: "Here", a: "I only tell", b: "the agent what I want.", prompt: "build the signup page", marks: { typed: "want" }, typedMark: "typed" },
|
|
35
|
+
{ kind: "phone", at: "Look", scale: 1 },
|
|
36
|
+
{
|
|
37
|
+
kind: "numbers", at: "In", chip: "48 hours",
|
|
38
|
+
marks: { c1: "31", c2: "12" },
|
|
39
|
+
counters: [{ to: 31, label: "commits", mark: "c1" }, { to: 12, label: "tests", mark: "c2" }],
|
|
40
|
+
},
|
|
41
|
+
{ kind: "steps", at: "Three", prompt: "three agents", items: ["writes", "tests", "reviews"], marks: { typed: "agents" }, typedMark: "typed" },
|
|
42
|
+
{
|
|
43
|
+
kind: "doc", at: "First", title: "spec.md", altTitle: "agent coding",
|
|
44
|
+
marks: { code: "then", review: "code" },
|
|
45
|
+
pages: [
|
|
46
|
+
{ lines: ["# Signup page", "", "- email and password", "- validate on submit", "- show errors inline", "- redirect to dashboard"] },
|
|
47
|
+
{ mono: true, mark: "code", lines: ["export async function signUp(", " input: SignUpInput", ") {", " const data = schema.parse(input)", " return createUser(data)", "}"] },
|
|
48
|
+
],
|
|
49
|
+
reviewers: { labels: ["correctness", "security", "regression", "scope"], mark: "review" },
|
|
50
|
+
},
|
|
51
|
+
{ kind: "cta", at: "Comment", a: "Comment", b: "SHIP", keyword: "SHIP", footer: "and I send you the prompt", hideCaptions: true },
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
export const exampleProps: EditorialReelProps = {
|
|
55
|
+
words: exampleWords as WordsData,
|
|
56
|
+
scenes: exampleScenes,
|
|
57
|
+
// Áudio entra por staticFile (arquivos em public/): descomente e troque os nomes.
|
|
58
|
+
// audio: { voice: "editorial/voice.wav", music: "editorial/music.mp3", sfx: [{ atWord: "Here", offset: -0.15, src: "editorial/swoosh.mp3", volume: 0.16 }] },
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const exampleSeconds = totalSeconds(exampleProps.words);
|
|
62
|
+
|
|
63
|
+
export const EditorialReel: React.FC<EditorialReelProps> = ({
|
|
64
|
+
words, scenes, theme, audio, overlap = 0.12, tailSeconds = 1.4,
|
|
65
|
+
}) => {
|
|
66
|
+
const merged = useMemo(() => mergeTheme(theme), [theme]);
|
|
67
|
+
const end = totalSeconds(words, tailSeconds);
|
|
68
|
+
const timed = useMemo(() => buildScenes(words.words, scenes, { end, overlap }), [words, scenes, end, overlap]);
|
|
69
|
+
const ctaStart = timed.find((s) => s.def.kind === "cta" && s.def.hideCaptions)?.from;
|
|
70
|
+
const sfx = useMemo(
|
|
71
|
+
() =>
|
|
72
|
+
(audio?.sfx ?? []).reduce<{ cursor: number; items: { from: number; def: SfxDef }[] }>(
|
|
73
|
+
(acc, def) => {
|
|
74
|
+
const t = at(words.words, def.atWord, acc.cursor);
|
|
75
|
+
return { cursor: t, items: [...acc.items, { from: Math.max(0, t + (def.offset ?? 0)), def }] };
|
|
76
|
+
},
|
|
77
|
+
{ cursor: 0, items: [] },
|
|
78
|
+
).items,
|
|
79
|
+
[audio, words],
|
|
80
|
+
);
|
|
81
|
+
const endFrame = toFrames(end);
|
|
82
|
+
const fadeOutFrame = toFrames(end - 2);
|
|
83
|
+
const musicVolume = audio?.musicVolume ?? 0.2;
|
|
84
|
+
return (
|
|
85
|
+
<ThemeContext.Provider value={merged}>
|
|
86
|
+
<AbsoluteFill>
|
|
87
|
+
<Backdrop />
|
|
88
|
+
{audio?.music ? (
|
|
89
|
+
<Audio
|
|
90
|
+
src={staticFile(audio.music)}
|
|
91
|
+
volume={(f) => interpolate(f, [0, 15, fadeOutFrame, endFrame], [0, musicVolume, musicVolume, 0], clamp)}
|
|
92
|
+
/>
|
|
93
|
+
) : null}
|
|
94
|
+
{audio?.voice ? <Audio src={staticFile(audio.voice)} /> : null}
|
|
95
|
+
{timed.map((s, i) => (
|
|
96
|
+
<Sequence key={i} from={toFrames(s.from)} durationInFrames={Math.max(1, toFrames(s.dur))}>
|
|
97
|
+
<Stage dur={s.dur}>
|
|
98
|
+
<SceneView scene={s.def} ctx={{ dur: s.dur, mark: (name, fb = 0) => (name ? s.marks[name] ?? fb : fb) }} />
|
|
99
|
+
</Stage>
|
|
100
|
+
</Sequence>
|
|
101
|
+
))}
|
|
102
|
+
{sfx.map((s, i) => (
|
|
103
|
+
<Sequence key={`sfx${i}`} from={toFrames(s.from)} durationInFrames={toFrames(s.def.durationSec ?? 1)}>
|
|
104
|
+
<Audio src={staticFile(s.def.src)} volume={s.def.volume ?? 0.16} />
|
|
105
|
+
</Sequence>
|
|
106
|
+
))}
|
|
107
|
+
<Captions words={words.words} hideFrom={ctaStart === undefined ? undefined : ctaStart} />
|
|
108
|
+
</AbsoluteFill>
|
|
109
|
+
</ThemeContext.Provider>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# remotion-editorial
|
|
2
|
+
|
|
3
|
+
Molde do estilo "motion editorial" (referência: shorts do Greg Isenberg): um objeto por vez no centro de um fundo claro com grade, cards escuros com sombra funda, serifa itálica bicolor, texto digitado em pílulas, conectores tracejados, música baixa e legenda pequena. Tudo guiado por palavras-âncora da fala (ctc-align).
|
|
4
|
+
|
|
5
|
+
Testado com Remotion 4.0.527, React 18, `@remotion/google-fonts` 4.0.527. Canvas 1080x1920, 30 fps. Sem outras dependências.
|
|
6
|
+
|
|
7
|
+
## Arquivos
|
|
8
|
+
|
|
9
|
+
| arquivo | papel |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `theme.ts` | `EditorialTheme`, `defaultTheme`, `mergeTheme`, `ThemeContext`, fontes (Playfair Display itálico, Inter, JetBrains Mono), `W/H/FPS` |
|
|
12
|
+
| `primitives.tsx` | `Stage`, `Backdrop`, `Headline`, `Rays`, `Ring`, `Pill`, `Chip`, `Dash`, `PhoneCard`, `Counter`, `DocCard` |
|
|
13
|
+
| `captions.tsx` | `Captions` (blocos de até 4 palavras, palavra falada em destaque) e `buildChunks` |
|
|
14
|
+
| `timeline.ts` | `at(words, texto, after)`, `resolveMarks`, `buildScenes` (âncoras + sobreposição), `totalSeconds` |
|
|
15
|
+
| `scenes.tsx` | tipo `SceneDef` (cenas como dados) e `SceneView` (7 tipos: hook, prompt, phone, numbers, steps, doc, cta) |
|
|
16
|
+
| `EditorialReel.tsx` | composição genérica + `exampleScenes`, `exampleProps`, `exampleSeconds` |
|
|
17
|
+
| `example-words.json` | palavras de exemplo no formato do ctc-align |
|
|
18
|
+
|
|
19
|
+
## Como usar
|
|
20
|
+
|
|
21
|
+
1. Copie a pasta inteira para `src/` do projeto Remotion (ex.: `src/editorial/`).
|
|
22
|
+
2. Registre no `Root.tsx`:
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { EditorialReel, exampleProps, exampleSeconds } from "./editorial/EditorialReel";
|
|
26
|
+
import { FPS, H, W } from "./editorial/theme";
|
|
27
|
+
|
|
28
|
+
<Composition
|
|
29
|
+
id="editorial-example"
|
|
30
|
+
component={EditorialReel}
|
|
31
|
+
defaultProps={exampleProps}
|
|
32
|
+
durationInFrames={Math.ceil(exampleSeconds * FPS)}
|
|
33
|
+
fps={FPS}
|
|
34
|
+
width={W}
|
|
35
|
+
height={H}
|
|
36
|
+
/>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
3. Troque as três entradas, nesta ordem:
|
|
40
|
+
- **palavras**: `words` = saída do ctc-align `{ words: [{ text, start, end }], total }`. Trocou a voz, realinhe o JSON; as cenas acompanham.
|
|
41
|
+
- **cenas**: array de `SceneDef`. Cada cena tem `at` (palavra onde ela começa) e `marks` opcional (nome -> palavra interna, ex.: quando o contador sobe). Palavra repetida: a busca é monotônica, então basta manter as cenas na ordem da fala. Palavra inexistente lança erro claro.
|
|
42
|
+
- **tema**: `theme={{ bg, accent, ... }}` parcial. Campos: `bg grid glow vignette ink card cardHi line onAccent accent accentWarm accentLight accentShadow dash shadow serif sans mono`.
|
|
43
|
+
4. Duração: `totalSeconds(words, tail)` = fim da fala + 1,4 s.
|
|
44
|
+
5. Áudio (opcional, arquivos em `public/`): `audio={{ voice: "x/voz.wav", music: "x/cama.mp3", musicVolume: 0.2, sfx: [{ atWord: "Here", offset: -0.15, src: "x/swoosh.mp3", volume: 0.16 }] }}`. Nomes passam por `staticFile`.
|
|
45
|
+
6. Screenshot: `{ kind: "phone", src: "x/tela.png", ratio: altura/largura }` (iPhone 1206x2622 = `2622/1206`, é o padrão). Sem `src`, desenha uma tela de exemplo. `src` também aceita URL `https:` ou `data:`.
|
|
46
|
+
7. Fontes: Playfair Display itálico (600/700) via `@remotion/google-fonts/PlayfairDisplay`, carregada em `theme.ts`.
|
|
47
|
+
|
|
48
|
+
## Tipos de cena
|
|
49
|
+
|
|
50
|
+
- `hook`: `a`, `b` (título bicolor) + anel girando.
|
|
51
|
+
- `prompt`: título + pílula digitada + raios; `typedMark` define até quando digita.
|
|
52
|
+
- `phone`: screenshot em card de celular.
|
|
53
|
+
- `numbers`: chip + até 2 contadores (`mark` = palavra em que cada um começa a subir).
|
|
54
|
+
- `steps`: pílula + raios + 3 chips ligados por tracejado.
|
|
55
|
+
- `doc`: card de documento com páginas (texto, depois código mono) e até 4 chips de revisão.
|
|
56
|
+
- `cta`: título + palavra-chave digitada + frase; `hideCaptions: true` some com a legenda dali em diante.
|
|
57
|
+
|
|
58
|
+
Para criar um tipo novo: componha as primitivas num componente em `scenes.tsx`, acrescente o ramo na união `SceneDef` e no `switch` de `SceneView`.
|
|
59
|
+
|
|
60
|
+
## Regras do estilo
|
|
61
|
+
|
|
62
|
+
- Um objeto por vez no centro. Faixa útil y 180 a 1300; o terço de baixo é da legenda.
|
|
63
|
+
- Troca por transformação, nunca corte seco: a cena sai subindo e sumindo enquanto a próxima entra por mola (`overlap` 0,12 s).
|
|
64
|
+
- Música baixa e contínua, volume ~0,2, com fade de entrada e de saída.
|
|
65
|
+
- SFX raro: no máximo ~1 a cada 10 s, volume baixo (~0,16), só em viradas grandes.
|
|
66
|
+
- Legenda pequena, sem contorno, palavra falada em destaque; some quando a cena já carrega texto grande.
|
|
67
|
+
- Serifa itálica só nos momentos-chave (títulos, pílulas, chips); sans na legenda e no corpo do documento; mono só em código.
|
|
68
|
+
- Cor de destaque única; sobre a cor de destaque use `onAccent` (claro).
|
|
69
|
+
- Timing sempre por âncora de palavra, nunca por segundo fixo.
|
|
70
|
+
|
|
71
|
+
## Verificar
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npx tsc --noEmit
|
|
75
|
+
npx remotion still src/index.ts editorial-example out/f.png --frame=330
|
|
76
|
+
```
|
|
77
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { interpolate } from "remotion";
|
|
3
|
+
import { clamp, useSeconds } from "./primitives";
|
|
4
|
+
import { useTheme } from "./theme";
|
|
5
|
+
import type { Word } from "./timeline";
|
|
6
|
+
|
|
7
|
+
// Legenda discreta: blocos curtos de até 4 palavras, sem contorno, palavra falada em destaque.
|
|
8
|
+
|
|
9
|
+
export type Chunk = { words: Word[]; start: number; end: number };
|
|
10
|
+
|
|
11
|
+
const BREAK = /[,.:!?]$/;
|
|
12
|
+
|
|
13
|
+
// Agrupa palavras em blocos: corta em 4 palavras, pontuação ou pausa > 0.35 s.
|
|
14
|
+
export const buildChunks = (all: readonly Word[], maxWords = 4): Chunk[] => {
|
|
15
|
+
const ws = all.filter((w) => w.text.trim());
|
|
16
|
+
const raw = ws.reduce<{ done: Chunk[]; cur: Word[] }>(
|
|
17
|
+
(acc, w, i) => {
|
|
18
|
+
const cur = [...acc.cur, w];
|
|
19
|
+
const next = ws[i + 1];
|
|
20
|
+
const cut = cur.length === maxWords || BREAK.test(w.text) || !next || next.start - w.end > 0.35;
|
|
21
|
+
return cut
|
|
22
|
+
? { done: [...acc.done, { words: cur, start: cur[0].start, end: w.end }], cur: [] }
|
|
23
|
+
: { ...acc, cur };
|
|
24
|
+
},
|
|
25
|
+
{ done: [], cur: [] },
|
|
26
|
+
).done;
|
|
27
|
+
// Cola o fim do bloco no começo do próximo quando a pausa é curta; senão segura 0,15 s.
|
|
28
|
+
return raw.map((c, i) => {
|
|
29
|
+
const next = raw[i + 1];
|
|
30
|
+
return { ...c, end: next && next.start - c.end < 0.5 ? next.start : c.end + 0.15 };
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const Captions: React.FC<{
|
|
35
|
+
words: readonly Word[];
|
|
36
|
+
hideFrom?: number; // s: some a partir daqui (ex.: cena final com texto próprio)
|
|
37
|
+
top?: number;
|
|
38
|
+
size?: number;
|
|
39
|
+
}> = ({ words, hideFrom, top = 1560, size = 50 }) => {
|
|
40
|
+
const c = useTheme();
|
|
41
|
+
const t = useSeconds();
|
|
42
|
+
const chunks = useMemo(() => buildChunks(words), [words]);
|
|
43
|
+
if (hideFrom !== undefined && t >= hideFrom) return null;
|
|
44
|
+
const chunk = chunks.find((x) => t >= x.start && t < x.end);
|
|
45
|
+
if (!chunk) return null;
|
|
46
|
+
const opacity = interpolate(t, [chunk.start, chunk.start + 0.1], [0, 1], clamp);
|
|
47
|
+
return (
|
|
48
|
+
<div style={{ position: "absolute", top, left: 90, right: 90, textAlign: "center", opacity, fontFamily: c.sans, fontWeight: 700, fontSize: size, lineHeight: 1.25, color: c.ink }}>
|
|
49
|
+
{chunk.words.map((w) => (
|
|
50
|
+
<span key={w.start} style={{ color: t >= w.start && t < w.end + 0.05 ? c.accent : c.ink }}>
|
|
51
|
+
{w.text.replace(BREAK, "")}{" "}
|
|
52
|
+
</span>
|
|
53
|
+
))}
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
@@ -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
|
+
}
|