CreatorKit Claude Opus 4.8 commited on
Commit
8661b79
·
0 Parent(s):

Initial commit: CreatorKit — plataforma para criadores

Browse files

Plataforma local com 7 ferramentas:
- Estudio de Video (transcricao Whisper -> 9 saidas de conteudo)
- Gerador de Hooks, Ideias, Roteiro, Hashtags, Bio e Calendario

Stack: Node.js + Express, Whisper (@xenova /transformers), yt-dlp, ffmpeg.
Pronto para producao: Dockerfile, helmet, compression, graceful
shutdown, health check, limpeza automatica de jobs e PORT via env.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files changed (12) hide show
  1. .claude/launch.json +11 -0
  2. .dockerignore +11 -0
  3. .gitignore +20 -0
  4. Dockerfile +28 -0
  5. README.md +65 -0
  6. analyze.js +478 -0
  7. package-lock.json +2047 -0
  8. package.json +26 -0
  9. server.js +293 -0
  10. start.bat +22 -0
  11. templates/index.html +955 -0
  12. whisper_worker.js +69 -0
.claude/launch.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "0.0.1",
3
+ "configurations": [
4
+ {
5
+ "name": "reel-transcriber",
6
+ "runtimeExecutable": "npm",
7
+ "runtimeArgs": ["start", "--prefix", "D:\\Claudio\\reel-transcriber"],
8
+ "port": 5050
9
+ }
10
+ ]
11
+ }
.dockerignore ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ node_modules
2
+ npm-debug.log
3
+ .git
4
+ .gitignore
5
+ downloads
6
+ uploads
7
+ bin
8
+ .cache
9
+ *.md
10
+ .env
11
+ .claude
.gitignore ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # dependencies
2
+ node_modules/
3
+
4
+ # runtime / temp
5
+ downloads/
6
+ uploads/
7
+ bin/
8
+ .cache/
9
+
10
+ # env
11
+ .env
12
+ .env.local
13
+
14
+ # logs
15
+ *.log
16
+ npm-debug.log*
17
+
18
+ # os
19
+ .DS_Store
20
+ Thumbs.db
Dockerfile ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CreatorKit — imagem de produção
2
+ # Debian slim (glibc) porque o onnxruntime do Whisper não roda bem em Alpine/musl.
3
+ FROM node:20-slim
4
+
5
+ # Dependências de sistema: ffmpeg (merge do yt-dlp) + python3 + yt-dlp standalone
6
+ RUN apt-get update && apt-get install -y --no-install-recommends \
7
+ python3 ffmpeg ca-certificates curl \
8
+ && curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp \
9
+ -o /usr/local/bin/yt-dlp \
10
+ && chmod a+rx /usr/local/bin/yt-dlp \
11
+ && apt-get clean && rm -rf /var/lib/apt/lists/*
12
+
13
+ WORKDIR /app
14
+
15
+ # Instala dependências (camada cacheável)
16
+ COPY package*.json ./
17
+ RUN npm install --omit=dev && npm cache clean --force
18
+
19
+ # Copia o restante do código
20
+ COPY . .
21
+
22
+ ENV NODE_ENV=production
23
+ ENV YTDLP_PATH=/usr/local/bin/yt-dlp
24
+ # Cache dos modelos Whisper dentro do app
25
+ ENV XENOVA_CACHE_DIR=/app/.cache
26
+
27
+ EXPOSE 5050
28
+ CMD ["node", "server.js"]
README.md ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ✦ CreatorKit
2
+
3
+ Plataforma local para criadores de conteúdo. Transcreve vídeos e gera caption, thread, post de blog, hooks, ideias, hashtags, bio, roteiro e calendário — tudo processado na própria máquina/servidor, sem API externa.
4
+
5
+ ## 🧰 Ferramentas
6
+
7
+ | Ferramenta | Precisa de vídeo? |
8
+ |---|---|
9
+ | 🎬 Estúdio de Vídeo (transcrição → 9 saídas) | Sim |
10
+ | 🪝 Gerador de Hooks | Não |
11
+ | 💡 Ideias de Conteúdo | Não |
12
+ | 📝 Roteiro de Vídeo | Não |
13
+ | #️⃣ Hashtags por Tema | Não |
14
+ | ✍️ Bio de Perfil | Não |
15
+ | 📅 Calendário de Posts | Não |
16
+
17
+ ## 🚀 Rodar localmente
18
+
19
+ Requisitos: **Node.js 18+**.
20
+
21
+ ```bash
22
+ npm install
23
+ npm start
24
+ ```
25
+
26
+ Acesse http://localhost:5050
27
+
28
+ O `yt-dlp` é baixado automaticamente na primeira execução. O `ffmpeg` já vem embutido via `ffmpeg-static`. O modelo Whisper (~250 MB no `small`) é baixado na primeira transcrição e fica em cache.
29
+
30
+ ## 🐳 Deploy com Docker
31
+
32
+ ```bash
33
+ docker build -t creatorkit .
34
+ docker run -p 5050:5050 creatorkit
35
+ ```
36
+
37
+ A imagem já inclui `ffmpeg`, `python3` e `yt-dlp`.
38
+
39
+ ## ☁️ Deploy em Render / Railway / Fly.io
40
+
41
+ O projeto está pronto para qualquer plataforma que rode Docker:
42
+
43
+ 1. Suba o repositório no GitHub.
44
+ 2. Crie um serviço apontando para o repo (deixe a plataforma detectar o `Dockerfile`).
45
+ 3. A porta é lida de `process.env.PORT` automaticamente.
46
+ 4. Health check disponível em `/health`.
47
+
48
+ ### Variáveis de ambiente (opcionais)
49
+
50
+ | Variável | Padrão | Descrição |
51
+ |---|---|---|
52
+ | `PORT` | `5050` | Porta do servidor |
53
+ | `YTDLP_PATH` | `/usr/local/bin/yt-dlp` (Docker) | Caminho do binário yt-dlp |
54
+ | `XENOVA_CACHE_DIR` | `/app/.cache` | Cache dos modelos Whisper |
55
+
56
+ ## ⚠️ Considerações de produção
57
+
58
+ - **Whisper é pesado de CPU.** Em instâncias pequenas (512 MB–1 GB), use os modelos `tiny` ou `base`. O `medium`/`large` exigem servidores com mais memória/CPU.
59
+ - **Downloads de Instagram/YouTube** podem ser bloqueados a partir de IPs de datacenter. Funciona melhor em IP residencial; em nuvem pode exigir cookies/login.
60
+ - O processamento é **single-instance** (jobs ficam em memória). Para escalar horizontalmente, seria necessário uma fila externa (ex.: Redis).
61
+ - Arquivos temporários e jobs antigos são limpos automaticamente a cada 15 min (TTL de 1 h).
62
+
63
+ ## 📄 Licença
64
+
65
+ ISC
analyze.js ADDED
@@ -0,0 +1,478 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * analyze.js — content generation engine for CreatorKit
3
+ * All processing is local — no external API needed.
4
+ */
5
+
6
+ // ── Stop words (PT + EN) ──────────────────────────────────────
7
+ const STOP_WORDS = new Set([
8
+ // PT
9
+ 'a','ao','aos','aquela','aquelas','aquele','aqueles','aquilo','as','até','com',
10
+ 'como','da','das','de','dela','delas','dele','deles','depois','do','dos','e',
11
+ 'ela','elas','ele','eles','em','entre','era','eram','essa','essas','esse',
12
+ 'esses','esta','estas','este','estes','eu','foi','for','foram','há','isso',
13
+ 'isto','já','lhe','lhes','mais','mas','me','mesmo','meu','minha','muito',
14
+ 'na','nas','nem','no','nos','nós','não','num','numa','o','os','ou','para',
15
+ 'pela','pelas','pelo','pelos','por','que','qual','quando','quem','são','se',
16
+ 'sem','ser','seu','seus','si','sua','suas','também','te','tem','ter','teu',
17
+ 'tua','tuas','tudo','um','uma','uns','umas','vai','vem','você','vocês','à',
18
+ 'às','é','isso','assim','então','agora','aqui','ali','lá','ainda','logo',
19
+ 'porque','então','pois','portanto','todavia','porém','entanto','contudo',
20
+ 'apenas','só','todo','toda','todos','todas','cada','outro','outra','outros',
21
+ 'outras','tal','tais','tanto','tantos','tanta','tantas','bem','mal','muito',
22
+ 'pouco','sempre','nunca','jamais','hoje','ontem','amanhã','quando','onde',
23
+ // EN
24
+ 'a','an','the','and','or','but','in','on','at','to','for','of','with',
25
+ 'as','is','was','are','were','be','been','being','have','has','had',
26
+ 'do','does','did','will','would','could','should','may','might','shall',
27
+ 'can','this','that','these','those','i','we','you','he','she','they','it',
28
+ 'my','our','your','his','her','their','its','me','us','him','them','who',
29
+ 'what','which','how','when','where','why','all','any','both','each',
30
+ 'few','more','most','other','some','such','no','not','only','same','so',
31
+ 'than','too','very','just','about','above','after','again','also','before',
32
+ 'between','here','into','like','now','out','over','own','then','there',
33
+ 'through','under','until','up','while','from','if',
34
+ ]);
35
+
36
+ // ── Tokenize & extract keywords ───────────────────────────────
37
+ function tokenize(text) {
38
+ return text
39
+ .toLowerCase()
40
+ .replace(/[^a-záàãâäéèêëíìîïóòõôöúùûüçñ\s]/gi, ' ')
41
+ .split(/\s+/)
42
+ .filter(w => w.length > 3 && !STOP_WORDS.has(w));
43
+ }
44
+
45
+ function extractKeywords(text, topN = 20) {
46
+ const words = tokenize(text);
47
+ const freq = {};
48
+ for (const w of words) freq[w] = (freq[w] || 0) + 1;
49
+ return Object.entries(freq)
50
+ .sort((a, b) => b[1] - a[1])
51
+ .slice(0, topN)
52
+ .map(([word, count]) => ({ word, count }));
53
+ }
54
+
55
+ // ── Extractive summary ────────────────────────────────────────
56
+ function summarize(text, numSentences = 4) {
57
+ const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
58
+ if (sentences.length <= numSentences) return text.trim();
59
+
60
+ const keywords = new Set(extractKeywords(text, 15).map(k => k.word));
61
+
62
+ const scored = sentences.map(s => {
63
+ const words = tokenize(s);
64
+ const score = words.filter(w => keywords.has(w)).length / (words.length || 1);
65
+ return { s: s.trim(), score };
66
+ });
67
+
68
+ return scored
69
+ .sort((a, b) => b.score - a.score)
70
+ .slice(0, numSentences)
71
+ .sort((a, b) => sentences.indexOf(a.s) - sentences.indexOf(b.s))
72
+ .map(x => x.s)
73
+ .join(' ');
74
+ }
75
+
76
+ // ── Hashtag generator ─────────────────────────────────────────
77
+ function generateHashtags(text, keywords) {
78
+ const tags = keywords
79
+ .slice(0, 12)
80
+ .map(k => '#' + k.word.replace(/\s+/g, '').normalize('NFD').replace(/[̀-ͯ]/g, ''));
81
+
82
+ // Detect topics and add contextual tags
83
+ const lower = text.toLowerCase();
84
+ const contextual = [];
85
+ if (/negócio|empresa|empreend|startup|produto|vend/.test(lower)) contextual.push('#empreendedorismo','#negócios','#business');
86
+ if (/saúde|treino|exercício|academia|fitness|dieta/.test(lower)) contextual.push('#saúde','#fitness','#bemestar');
87
+ if (/tecnologia|tech|software|programação|código|ia|inteligência/.test(lower)) contextual.push('#tecnologia','#tech','#inovação');
88
+ if (/motivação|sucesso|crescimento|mindset|foco/.test(lower)) contextual.push('#motivação','#sucesso','#mindset');
89
+ if (/receita|cozinha|comida|culinária|alimento/.test(lower)) contextual.push('#gastronomia','#culinária','#foodlover');
90
+ if (/moda|roupa|estilo|fashion|look/.test(lower)) contextual.push('#moda','#fashion','#estilo');
91
+ if (/viagem|destino|turismo|lugar|cidade/.test(lower)) contextual.push('#viagem','#turismo','#travel');
92
+ if (/financ|invest|dinheiro|renda|rico/.test(lower)) contextual.push('#finanças','#investimento','#dinheiro');
93
+ if (/relacionamento|amor|família|casal|vida/.test(lower)) contextual.push('#relacionamentos','#vidapessoal');
94
+
95
+ const all = [...new Set([...tags, ...contextual, '#conteúdo', '#influencer', '#viral'])];
96
+ return all.slice(0, 25);
97
+ }
98
+
99
+ // ── Instagram Caption ─────────────────────────────────────────
100
+ function generateInstagramCaption(text, keywords, hashtags, lang) {
101
+ const summary = summarize(text, 2);
102
+ const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
103
+ const hook = sentences[0]?.trim() || summary.split('.')[0];
104
+ const kws = keywords.slice(0, 3).map(k => k.word);
105
+
106
+ const ctas = [
107
+ '💬 Me conta o que você achou nos comentários!',
108
+ '👇 Qual foi o ponto que mais te chamou atenção?',
109
+ '🔁 Salva esse conteúdo para não perder!',
110
+ '📲 Manda pra um amigo que precisa ver isso!',
111
+ '✨ Segue para mais conteúdo como esse!',
112
+ ];
113
+ const cta = ctas[Math.floor(Math.random() * ctas.length)];
114
+
115
+ const topHashtags = hashtags.slice(0, 15).join(' ');
116
+
117
+ return `${hook}
118
+
119
+ ✨ Neste conteúdo você vai ver:
120
+ ${kws.map(k => `▸ ${k.charAt(0).toUpperCase() + k.slice(1)}`).join('\n')}
121
+
122
+ ${summary}
123
+
124
+ ${cta}
125
+
126
+ .
127
+ .
128
+ .
129
+ ${topHashtags}`;
130
+ }
131
+
132
+ // ── YouTube Kit ───────────────────────────────────────────────
133
+ function generateYouTubeKit(text, keywords, segments) {
134
+ const kws = keywords.slice(0, 5).map(k => k.word);
135
+
136
+ // Title variations
137
+ const firstSentence = (text.match(/[^.!?]+[.!?]+/)?.[0] || '').trim().slice(0, 60);
138
+ const titles = [
139
+ `${firstSentence || kws.slice(0,2).join(' e ')} | Conteúdo Completo`,
140
+ `TUDO sobre ${kws[0] || 'esse tema'} que você precisa saber`,
141
+ `${kws.slice(0,2).join(', ')} e muito mais — Veja agora!`,
142
+ ];
143
+
144
+ // Description
145
+ const summary = summarize(text, 3);
146
+ const tags = keywords.slice(0, 15).map(k => k.word).join(', ');
147
+
148
+ // Timestamps from segments
149
+ let timestampBlock = '';
150
+ if (segments && segments.length > 0) {
151
+ const interval = Math.max(1, Math.floor(segments.length / 8));
152
+ const picks = segments.filter((_, i) => i % interval === 0).slice(0, 8);
153
+ timestampBlock = '\n\n⏱️ CAPÍTULOS:\n' + picks.map(s => `${fmtTime(s.start)} ${s.text.slice(0, 50)}`).join('\n');
154
+ }
155
+
156
+ const description = `📌 ${summary}
157
+ ${timestampBlock}
158
+
159
+ 🔔 Inscreva-se no canal e ative o sininho para não perder nenhum vídeo!
160
+ 👍 Se o conteúdo foi útil, deixa o like — ajuda muito!
161
+ 💬 Tem alguma dúvida? Comenta aqui embaixo!
162
+
163
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
164
+ 🔗 Minhas redes sociais:
165
+ ▸ Instagram: @seuinstagram
166
+ ▸ TikTok: @seutiktok
167
+ ▸ Twitter: @seutwitter
168
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
169
+
170
+ #${kws.join(' #')}`;
171
+
172
+ return { titles, description, tags };
173
+ }
174
+
175
+ // ── Twitter/X Thread ──────────────────────────────────────────
176
+ function generateTwitterThread(text, keywords) {
177
+ const sentences = (text.match(/[^.!?]+[.!?]+/g) || [text]).map(s => s.trim()).filter(Boolean);
178
+ const summary = summarize(text, 6);
179
+ const sumSentences = (summary.match(/[^.!?]+[.!?]+/g) || [summary]).map(s => s.trim()).filter(Boolean);
180
+
181
+ const tweets = [];
182
+ const kws = keywords.slice(0, 3).map(k => k.word);
183
+
184
+ // Tweet 1: hook
185
+ tweets.push(`🧵 ${sentences[0] || kws.join(', ')}\n\nThread completa 👇`);
186
+
187
+ // Middle tweets: pack sentences into 250 chars each
188
+ let current = '';
189
+ for (const s of sumSentences) {
190
+ if ((current + ' ' + s).length > 250) {
191
+ if (current) tweets.push(current.trim());
192
+ current = s;
193
+ } else {
194
+ current = current ? current + ' ' + s : s;
195
+ }
196
+ }
197
+ if (current) tweets.push(current.trim());
198
+
199
+ // Last tweet: CTA
200
+ tweets.push(`📌 Gostou desse conteúdo?\n\n↩️ RT para compartilhar\n❤️ Like se foi útil\n🔔 Siga para mais\n\n#${kws.slice(0,3).join(' #')}`);
201
+
202
+ return tweets.map((t, i) => `${i + 1}/${tweets.length}\n\n${t}`);
203
+ }
204
+
205
+ // ── LinkedIn Post ─────────────────────────────────────────────
206
+ function generateLinkedIn(text, keywords) {
207
+ const sentences = (text.match(/[^.!?]+[.!?]+/g) || [text]).map(s => s.trim());
208
+ const kws = keywords.slice(0, 5);
209
+ const summary = summarize(text, 4);
210
+
211
+ return `💡 ${sentences[0] || 'Reflexão importante'}
212
+
213
+ ${summary}
214
+
215
+ 3 pontos principais sobre ${kws[0]?.word || 'esse tema'}:
216
+
217
+ → ${sentences[1]?.trim() || kws[0]?.word || ''}
218
+ → ${sentences[2]?.trim() || kws[1]?.word || ''}
219
+ → ${sentences[3]?.trim() || kws[2]?.word || ''}
220
+
221
+ O que você pensa sobre isso? Compartilhe nos comentários. 👇
222
+
223
+ ━━━━━━━━━━━━━━━━━━━━━
224
+
225
+ ${kws.slice(0, 5).map(k => `#${k.word}`).join(' ')} #linkedin #networking #conteúdo`;
226
+ }
227
+
228
+ // ── Blog Post ─────────────────────────────────────────────────
229
+ function generateBlogPost(text, keywords, title) {
230
+ const sentences = (text.match(/[^.!?]+[.!?]+/g) || [text]).map(s => s.trim()).filter(Boolean);
231
+ const kws = keywords.slice(0, 6).map(k => k.word);
232
+ const postTitle = title || `${kws[0]?.charAt(0).toUpperCase() + kws[0]?.slice(1) || 'Conteúdo'}: tudo o que você precisa saber`;
233
+
234
+ // Split into chunks of ~3 sentences per section
235
+ const sections = [];
236
+ let chunk = [];
237
+ for (const s of sentences) {
238
+ chunk.push(s);
239
+ if (chunk.length === 3) { sections.push(chunk.join(' ')); chunk = []; }
240
+ }
241
+ if (chunk.length) sections.push(chunk.join(' '));
242
+
243
+ const sectionTitles = [
244
+ 'Introdução', 'Por que isso importa', 'Principais pontos',
245
+ 'Como aplicar na prática', 'Dicas e insights', 'Conclusão',
246
+ ];
247
+
248
+ let post = `# ${postTitle}\n\n`;
249
+ post += `*Tempo de leitura: ~${Math.ceil(text.split(/\s+/).length / 200)} min*\n\n`;
250
+ post += `---\n\n`;
251
+
252
+ sections.slice(0, 5).forEach((section, i) => {
253
+ post += `## ${sectionTitles[i] || `Parte ${i + 1}`}\n\n${section}\n\n`;
254
+ });
255
+
256
+ post += `## Conclusão\n\n${summarize(text, 2)}\n\n`;
257
+ post += `---\n\n**Tags:** ${kws.join(', ')}`;
258
+
259
+ return post;
260
+ }
261
+
262
+ // ── Main analyze function ─────────────────────────────────────
263
+ function analyze(text, segments = [], videoTitle = '') {
264
+ if (!text || text.trim().length < 10) throw new Error('Texto muito curto para análise.');
265
+
266
+ const keywords = extractKeywords(text, 20);
267
+ const hashtags = generateHashtags(text, keywords);
268
+ const summary = summarize(text, 4);
269
+ const instagram = generateInstagramCaption(text, keywords, hashtags);
270
+ const yt = generateYouTubeKit(text, keywords, segments);
271
+ const thread = generateTwitterThread(text, keywords);
272
+ const linkedin = generateLinkedIn(text, keywords);
273
+ const blog = generateBlogPost(text, keywords, videoTitle);
274
+
275
+ const wordCount = text.trim().split(/\s+/).length;
276
+ const readingTime = Math.ceil(wordCount / 200);
277
+
278
+ return {
279
+ summary,
280
+ keywords: keywords.slice(0, 12),
281
+ hashtags,
282
+ instagram,
283
+ youtube: yt,
284
+ twitter: thread,
285
+ linkedin,
286
+ blog,
287
+ stats: { wordCount, readingTime, sentenceCount: (text.match(/[.!?]+/g) || []).length },
288
+ };
289
+ }
290
+
291
+ function fmtTime(s) {
292
+ const m = Math.floor(s / 60), sec = Math.floor(s % 60).toString().padStart(2,'0');
293
+ return `${m}:${sec}`;
294
+ }
295
+
296
+ // ══════════════════════════════════════════════════════════════
297
+ // STANDALONE CREATOR TOOLS (topic-based, no video needed)
298
+ // ══════════════════════════════════════════════════════════════
299
+
300
+ function pick(arr, n) {
301
+ const copy = [...arr];
302
+ const out = [];
303
+ while (out.length < n && copy.length) {
304
+ out.push(copy.splice(Math.floor(Math.random() * copy.length), 1)[0]);
305
+ }
306
+ return out;
307
+ }
308
+
309
+ const cap = s => s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
310
+
311
+ // ── 🪝 HOOKS ──────────────────────────────────────────────────
312
+ function generateHooks(topic) {
313
+ if (!topic || !topic.trim()) throw new Error('Informe um tópico.');
314
+ const t = topic.trim().toLowerCase();
315
+ const T = cap(t);
316
+
317
+ const templates = [
318
+ `Pare de errar em ${t} — faça assim a partir de hoje.`,
319
+ `Ninguém te conta a verdade sobre ${t}...`,
320
+ `Eu perdi anos errando em ${t}. Não cometa o mesmo erro.`,
321
+ `3 verdades sobre ${t} que mudaram tudo pra mim.`,
322
+ `O segredo de ${t} que os experts não compartilham.`,
323
+ `Como eu dominei ${t} em apenas 30 dias.`,
324
+ `A maioria das pessoas erra em ${t}. Veja como acertar.`,
325
+ `${T} não é o que você imagina. Olha só isso.`,
326
+ `Faça ISSO antes de tentar qualquer coisa com ${t}.`,
327
+ `O maior mito sobre ${t} — desmascarado.`,
328
+ `Se você quer resultado com ${t}, assista até o final.`,
329
+ `Eu testei tudo sobre ${t} pra você não precisar.`,
330
+ `Esse erro em ${t} está te custando caro (e você nem percebe).`,
331
+ `${T} em 60 segundos: o guia que você precisava.`,
332
+ `Por que 90% das pessoas falham em ${t}?`,
333
+ `O que eu faria se tivesse que começar do zero em ${t}.`,
334
+ ];
335
+ return { topic, hooks: pick(templates, 10) };
336
+ }
337
+
338
+ // ── 💡 CONTENT IDEAS ──────────────────────────────────────────
339
+ function generateIdeas(niche) {
340
+ if (!niche || !niche.trim()) throw new Error('Informe seu nicho.');
341
+ const n = niche.trim().toLowerCase();
342
+ const N = cap(n);
343
+
344
+ const ideas = [
345
+ { fmt: 'Reels', icon: '🎬', title: `Top 5 erros que iniciantes cometem em ${n}` },
346
+ { fmt: 'Carrossel', icon: '🖼️', title: `Mitos vs verdades sobre ${n}` },
347
+ { fmt: 'Reels', icon: '🎬', title: `Um dia na vida de quem trabalha com ${n}` },
348
+ { fmt: 'YouTube', icon: '▶️', title: `Tutorial passo a passo de ${n} para iniciantes` },
349
+ { fmt: 'Carrossel', icon: '🖼️', title: `O que eu gostaria de saber antes de começar em ${n}` },
350
+ { fmt: 'Story', icon: '📲', title: `Caixinha de perguntas: tudo sobre ${n}` },
351
+ { fmt: 'Reels', icon: '🎬', title: `Antes e depois: minha evolução em ${n}` },
352
+ { fmt: 'Carrossel', icon: '🖼️', title: `5 ferramentas essenciais para ${n}` },
353
+ { fmt: 'YouTube', icon: '▶️', title: `Reagindo às maiores tendências de ${n}` },
354
+ { fmt: 'Reels', icon: '🎬', title: `Desafio de 30 dias de ${n} — resultados reais` },
355
+ { fmt: 'Carrossel', icon: '🖼️', title: `Guia rápido: ${n} do zero ao avançado` },
356
+ { fmt: 'Reels', icon: '🎬', title: `3 hábitos que transformaram meu ${n}` },
357
+ { fmt: 'Story', icon: '📲', title: `Bastidores: como eu produzo conteúdo de ${n}` },
358
+ { fmt: 'YouTube', icon: '▶️', title: `${N}: vale a pena em 2026? Minha opinião sincera` },
359
+ { fmt: 'Carrossel', icon: '🖼️', title: `Os 7 termos de ${n} que todo mundo deveria saber` },
360
+ ];
361
+ return { niche, ideas: pick(ideas, 9) };
362
+ }
363
+
364
+ // ── ✍️ BIO GENERATOR ──────────────────────────────────────────
365
+ function generateBio({ name = '', niche = '', cta = '' }) {
366
+ if (!niche.trim()) throw new Error('Informe seu nicho.');
367
+ const n = niche.trim();
368
+ const link = cta.trim() || '👇 Link abaixo';
369
+
370
+ const bios = [
371
+ `✨ ${name || 'Criador(a)'} | ${cap(n)}\n📍 Conteúdo diário sobre ${n.toLowerCase()}\n💡 Dicas que realmente funcionam\n${link}`,
372
+ `${cap(n)} descomplicado 🚀\nAjudo você a evoluir em ${n.toLowerCase()}\n🎯 Novo conteúdo toda semana\n${link}`,
373
+ `${name ? name + ' • ' : ''}Apaixonado(a) por ${n.toLowerCase()} 💜\nTransformando conhecimento em resultado\n📲 Me segue pra não perder nada\n${link}`,
374
+ `Seu cantinho de ${n.toLowerCase()} 🌟\n✅ Conteúdo prático e direto\n✅ Sem enrolação\n${link}`,
375
+ ];
376
+ return { bios };
377
+ }
378
+
379
+ // ── 📅 CONTENT CALENDAR ───────────────────────────────────────
380
+ function generateCalendar({ niche = '', perWeek = 5 }) {
381
+ if (!niche.trim()) throw new Error('Informe seu nicho.');
382
+ const n = niche.trim().toLowerCase();
383
+ const days = ['Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado', 'Domingo'];
384
+ const blocks = [
385
+ { icon: '🎬', fmt: 'Reels', theme: `Dica rápida de ${n}`, best: '12h ou 19h' },
386
+ { icon: '🖼️', fmt: 'Carrossel', theme: `Passo a passo sobre ${n}`, best: '18h' },
387
+ { icon: '📲', fmt: 'Story', theme: `Bastidores + enquete de ${n}`, best: 'Ao longo do dia' },
388
+ { icon: '🎬', fmt: 'Reels', theme: `Mito vs verdade de ${n}`, best: '20h' },
389
+ { icon: '▶️', fmt: 'YouTube', theme: `Vídeo aprofundado de ${n}`, best: 'Sábado 10h' },
390
+ { icon: '🖼️', fmt: 'Carrossel', theme: `Lista: top ferramentas de ${n}`, best: '17h' },
391
+ { icon: '📲', fmt: 'Story', theme: `Caixinha de perguntas de ${n}`, best: 'Domingo' },
392
+ ];
393
+ const count = Math.min(Math.max(parseInt(perWeek) || 5, 1), 7);
394
+ const plan = days.slice(0, count).map((d, i) => ({ day: d, ...blocks[i % blocks.length] }));
395
+ return { niche, plan };
396
+ }
397
+
398
+ // ── 📸 CAPTION FROM TOPIC ─────────────────────────────────────
399
+ function generateCaptionFromTopic(topic) {
400
+ if (!topic || !topic.trim()) throw new Error('Informe um tópico.');
401
+ const t = topic.trim();
402
+ const keywords = extractKeywords(t + ' ' + t, 10);
403
+ const hashtags = generateHashtags(t, keywords);
404
+ const ctas = [
405
+ '💬 Comenta aqui o que você achou!',
406
+ '🔁 Salva esse post para não esquecer!',
407
+ '📲 Marca um amigo que precisa ver isso!',
408
+ '✨ Segue pra mais conteúdo como esse!',
409
+ ];
410
+ const caption = `${cap(t)} 🚀\n\nVocê sabia disso? Deixa eu te explicar de um jeito simples 👇\n\n▸ Ponto 1 sobre ${t.toLowerCase()}\n▸ Ponto 2 que faz a diferença\n▸ Ponto 3 que ninguém te conta\n\n${pick(ctas, 1)[0]}\n\n.\n.\n.\n${hashtags.slice(0, 15).join(' ')}`;
411
+ return { caption, hashtags };
412
+ }
413
+
414
+ // ── 📝 SCRIPT / ROTEIRO ───────────────────────────────────────
415
+ function generateScript({ topic = '', format = 'reels' }) {
416
+ if (!topic.trim()) throw new Error('Informe o tema do vídeo.');
417
+ const t = topic.trim().toLowerCase();
418
+ const T = cap(t);
419
+
420
+ const hooks = [
421
+ `Pare tudo: se você se interessa por ${t}, precisa ver isso.`,
422
+ `Ninguém te conta a verdade sobre ${t}...`,
423
+ `Eu errei MUITO em ${t} até descobrir isso.`,
424
+ `Esse erro em ${t} está te custando caro.`,
425
+ `${T} em 30 segundos — presta atenção.`,
426
+ ];
427
+ const ctas = [
428
+ `Salva esse vídeo pra não esquecer e segue pra mais conteúdo sobre ${t}! 🚀`,
429
+ `Comenta aqui embaixo a sua maior dúvida sobre ${t}. Eu respondo todo mundo! 💬`,
430
+ `Marca aquele amigo que precisa ver isso e me segue pra mais dicas! 📲`,
431
+ ];
432
+
433
+ const isYT = format === 'youtube';
434
+
435
+ const sections = [
436
+ { label: '🎣 Gancho (0–3s)', tip: 'Fale isso olhando pra câmera, com energia.', text: pick(hooks, 1)[0] },
437
+ { label: '🎬 Contexto (3–10s)', tip: 'Mostre que você entende a dor de quem assiste.', text: `Se você sente que ${t} é complicado ou já tentou e não deu certo, relaxa — eu vou simplificar tudo pra você agora.` },
438
+ { label: '📍 Ponto 1', tip: 'Primeira ideia prática.', text: `O primeiro passo em ${t} é entender o fundamento. A maioria pula essa parte e é por isso que trava.` },
439
+ { label: '📍 Ponto 2', tip: 'Segunda ideia, aprofunde.', text: `Depois disso, foque em consistência. Pequenas ações repetidas valem mais do que grandes esforços isolados.` },
440
+ { label: '📍 Ponto 3', tip: 'Terceira ideia, a mais forte.', text: `E o pulo do gato: ${t} funciona muito melhor quando você acompanha os resultados e ajusta no caminho.` },
441
+ { label: '🔥 Virada', tip: 'O insight que prende até o fim.', text: `Mas o que quase ninguém te conta é que ${t} não é sobre fazer mais — é sobre fazer o certo, de forma constante.` },
442
+ { label: '📢 Chamada pra ação', tip: 'Diga exatamente o que fazer agora.', text: pick(ctas, 1)[0] },
443
+ ];
444
+
445
+ if (isYT) {
446
+ sections.splice(2, 0, { label: '👋 Intro do canal (10–20s)', tip: 'Só no YouTube — apresente-se rápido.', text: `Se é a primeira vez aqui no canal, seja muito bem-vindo! Aqui a gente fala sobre ${t} de um jeito direto e sem enrolação. Bora?` });
447
+ }
448
+
449
+ const full = sections.map(s => `${s.label}\n${s.text}`).join('\n\n');
450
+ const wordCount = full.split(/\s+/).length;
451
+ const estSeconds = Math.round(wordCount / 2.5); // ~150 wpm falado
452
+
453
+ return { topic, format, sections, full, estSeconds };
454
+ }
455
+
456
+ // ── #️⃣ HASHTAGS FROM TOPIC ────────────────────────────────────
457
+ function generateHashtagsFromTopic(topic) {
458
+ if (!topic || !topic.trim()) throw new Error('Informe um tópico.');
459
+ const keywords = extractKeywords(topic + ' ' + topic, 12);
460
+ const hashtags = generateHashtags(topic, keywords);
461
+ return { hashtags };
462
+ }
463
+
464
+ // ── DISPATCHER ────────────────────────────────────────────────
465
+ function generateTools(type, params = {}) {
466
+ switch (type) {
467
+ case 'hooks': return generateHooks(params.topic);
468
+ case 'ideas': return generateIdeas(params.niche);
469
+ case 'bio': return generateBio(params);
470
+ case 'calendar': return generateCalendar(params);
471
+ case 'caption': return generateCaptionFromTopic(params.topic);
472
+ case 'script': return generateScript(params);
473
+ case 'hashtags': return generateHashtagsFromTopic(params.topic);
474
+ default: throw new Error('Ferramenta desconhecida: ' + type);
475
+ }
476
+ }
477
+
478
+ module.exports = { analyze, generateTools };
package-lock.json ADDED
@@ -0,0 +1,2047 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "reel-transcriber",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "reel-transcriber",
9
+ "version": "1.0.0",
10
+ "license": "ISC",
11
+ "dependencies": {
12
+ "@xenova/transformers": "^2.17.2",
13
+ "compression": "^1.8.1",
14
+ "express": "^5.2.1",
15
+ "ffmpeg-static": "^5.3.0",
16
+ "fluent-ffmpeg": "^2.1.3",
17
+ "helmet": "^8.2.0",
18
+ "multer": "^2.1.1",
19
+ "yt-dlp-wrap": "^2.3.12"
20
+ }
21
+ },
22
+ "node_modules/@derhuerst/http-basic": {
23
+ "version": "8.2.4",
24
+ "resolved": "https://registry.npmjs.org/@derhuerst/http-basic/-/http-basic-8.2.4.tgz",
25
+ "integrity": "sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==",
26
+ "license": "MIT",
27
+ "dependencies": {
28
+ "caseless": "^0.12.0",
29
+ "concat-stream": "^2.0.0",
30
+ "http-response-object": "^3.0.1",
31
+ "parse-cache-control": "^1.0.1"
32
+ },
33
+ "engines": {
34
+ "node": ">=6.0.0"
35
+ }
36
+ },
37
+ "node_modules/@huggingface/jinja": {
38
+ "version": "0.2.2",
39
+ "resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.2.2.tgz",
40
+ "integrity": "sha512-/KPde26khDUIPkTGU82jdtTW9UAuvUTumCAbFs/7giR0SxsvZC4hru51PBvpijH6BVkHcROcvZM/lpy5h1jRRA==",
41
+ "license": "MIT",
42
+ "engines": {
43
+ "node": ">=18"
44
+ }
45
+ },
46
+ "node_modules/@protobufjs/aspromise": {
47
+ "version": "1.1.2",
48
+ "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
49
+ "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
50
+ "license": "BSD-3-Clause"
51
+ },
52
+ "node_modules/@protobufjs/base64": {
53
+ "version": "1.1.2",
54
+ "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
55
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
56
+ "license": "BSD-3-Clause"
57
+ },
58
+ "node_modules/@protobufjs/codegen": {
59
+ "version": "2.0.5",
60
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
61
+ "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
62
+ "license": "BSD-3-Clause"
63
+ },
64
+ "node_modules/@protobufjs/eventemitter": {
65
+ "version": "1.1.1",
66
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
67
+ "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
68
+ "license": "BSD-3-Clause"
69
+ },
70
+ "node_modules/@protobufjs/fetch": {
71
+ "version": "1.1.1",
72
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
73
+ "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
74
+ "license": "BSD-3-Clause",
75
+ "dependencies": {
76
+ "@protobufjs/aspromise": "^1.1.1"
77
+ }
78
+ },
79
+ "node_modules/@protobufjs/float": {
80
+ "version": "1.0.2",
81
+ "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
82
+ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
83
+ "license": "BSD-3-Clause"
84
+ },
85
+ "node_modules/@protobufjs/inquire": {
86
+ "version": "1.1.2",
87
+ "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz",
88
+ "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==",
89
+ "license": "BSD-3-Clause"
90
+ },
91
+ "node_modules/@protobufjs/path": {
92
+ "version": "1.1.2",
93
+ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
94
+ "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
95
+ "license": "BSD-3-Clause"
96
+ },
97
+ "node_modules/@protobufjs/pool": {
98
+ "version": "1.1.0",
99
+ "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
100
+ "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
101
+ "license": "BSD-3-Clause"
102
+ },
103
+ "node_modules/@protobufjs/utf8": {
104
+ "version": "1.1.1",
105
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz",
106
+ "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==",
107
+ "license": "BSD-3-Clause"
108
+ },
109
+ "node_modules/@types/long": {
110
+ "version": "4.0.2",
111
+ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
112
+ "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
113
+ "license": "MIT"
114
+ },
115
+ "node_modules/@types/node": {
116
+ "version": "10.17.60",
117
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
118
+ "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==",
119
+ "license": "MIT"
120
+ },
121
+ "node_modules/@xenova/transformers": {
122
+ "version": "2.17.2",
123
+ "resolved": "https://registry.npmjs.org/@xenova/transformers/-/transformers-2.17.2.tgz",
124
+ "integrity": "sha512-lZmHqzrVIkSvZdKZEx7IYY51TK0WDrC8eR0c5IMnBsO8di8are1zzw8BlLhyO2TklZKLN5UffNGs1IJwT6oOqQ==",
125
+ "license": "Apache-2.0",
126
+ "dependencies": {
127
+ "@huggingface/jinja": "^0.2.2",
128
+ "onnxruntime-web": "1.14.0",
129
+ "sharp": "^0.32.0"
130
+ },
131
+ "optionalDependencies": {
132
+ "onnxruntime-node": "1.14.0"
133
+ }
134
+ },
135
+ "node_modules/accepts": {
136
+ "version": "2.0.0",
137
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
138
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
139
+ "license": "MIT",
140
+ "dependencies": {
141
+ "mime-types": "^3.0.0",
142
+ "negotiator": "^1.0.0"
143
+ },
144
+ "engines": {
145
+ "node": ">= 0.6"
146
+ }
147
+ },
148
+ "node_modules/agent-base": {
149
+ "version": "6.0.2",
150
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
151
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
152
+ "license": "MIT",
153
+ "dependencies": {
154
+ "debug": "4"
155
+ },
156
+ "engines": {
157
+ "node": ">= 6.0.0"
158
+ }
159
+ },
160
+ "node_modules/append-field": {
161
+ "version": "1.0.0",
162
+ "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
163
+ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
164
+ "license": "MIT"
165
+ },
166
+ "node_modules/async": {
167
+ "version": "0.2.10",
168
+ "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
169
+ "integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ=="
170
+ },
171
+ "node_modules/b4a": {
172
+ "version": "1.8.1",
173
+ "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz",
174
+ "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==",
175
+ "license": "Apache-2.0",
176
+ "peerDependencies": {
177
+ "react-native-b4a": "*"
178
+ },
179
+ "peerDependenciesMeta": {
180
+ "react-native-b4a": {
181
+ "optional": true
182
+ }
183
+ }
184
+ },
185
+ "node_modules/bare-events": {
186
+ "version": "2.9.1",
187
+ "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz",
188
+ "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==",
189
+ "license": "Apache-2.0",
190
+ "peerDependencies": {
191
+ "bare-abort-controller": "*"
192
+ },
193
+ "peerDependenciesMeta": {
194
+ "bare-abort-controller": {
195
+ "optional": true
196
+ }
197
+ }
198
+ },
199
+ "node_modules/bare-fs": {
200
+ "version": "4.7.2",
201
+ "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.2.tgz",
202
+ "integrity": "sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg==",
203
+ "license": "Apache-2.0",
204
+ "dependencies": {
205
+ "bare-events": "^2.5.4",
206
+ "bare-path": "^3.0.0",
207
+ "bare-stream": "^2.6.4",
208
+ "bare-url": "^2.2.2",
209
+ "fast-fifo": "^1.3.2"
210
+ },
211
+ "engines": {
212
+ "bare": ">=1.16.0"
213
+ },
214
+ "peerDependencies": {
215
+ "bare-buffer": "*"
216
+ },
217
+ "peerDependenciesMeta": {
218
+ "bare-buffer": {
219
+ "optional": true
220
+ }
221
+ }
222
+ },
223
+ "node_modules/bare-os": {
224
+ "version": "3.9.1",
225
+ "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.1.tgz",
226
+ "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==",
227
+ "license": "Apache-2.0",
228
+ "engines": {
229
+ "bare": ">=1.14.0"
230
+ }
231
+ },
232
+ "node_modules/bare-path": {
233
+ "version": "3.0.1",
234
+ "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.1.tgz",
235
+ "integrity": "sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==",
236
+ "license": "Apache-2.0",
237
+ "dependencies": {
238
+ "bare-os": "^3.0.1"
239
+ }
240
+ },
241
+ "node_modules/bare-stream": {
242
+ "version": "2.13.1",
243
+ "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz",
244
+ "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==",
245
+ "license": "Apache-2.0",
246
+ "dependencies": {
247
+ "streamx": "^2.25.0",
248
+ "teex": "^1.0.1"
249
+ },
250
+ "peerDependencies": {
251
+ "bare-abort-controller": "*",
252
+ "bare-buffer": "*",
253
+ "bare-events": "*"
254
+ },
255
+ "peerDependenciesMeta": {
256
+ "bare-abort-controller": {
257
+ "optional": true
258
+ },
259
+ "bare-buffer": {
260
+ "optional": true
261
+ },
262
+ "bare-events": {
263
+ "optional": true
264
+ }
265
+ }
266
+ },
267
+ "node_modules/bare-url": {
268
+ "version": "2.4.3",
269
+ "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.3.tgz",
270
+ "integrity": "sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==",
271
+ "license": "Apache-2.0",
272
+ "dependencies": {
273
+ "bare-path": "^3.0.0"
274
+ }
275
+ },
276
+ "node_modules/base64-js": {
277
+ "version": "1.5.1",
278
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
279
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
280
+ "funding": [
281
+ {
282
+ "type": "github",
283
+ "url": "https://github.com/sponsors/feross"
284
+ },
285
+ {
286
+ "type": "patreon",
287
+ "url": "https://www.patreon.com/feross"
288
+ },
289
+ {
290
+ "type": "consulting",
291
+ "url": "https://feross.org/support"
292
+ }
293
+ ],
294
+ "license": "MIT"
295
+ },
296
+ "node_modules/bl": {
297
+ "version": "4.1.0",
298
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
299
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
300
+ "license": "MIT",
301
+ "dependencies": {
302
+ "buffer": "^5.5.0",
303
+ "inherits": "^2.0.4",
304
+ "readable-stream": "^3.4.0"
305
+ }
306
+ },
307
+ "node_modules/body-parser": {
308
+ "version": "2.2.2",
309
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
310
+ "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
311
+ "license": "MIT",
312
+ "dependencies": {
313
+ "bytes": "^3.1.2",
314
+ "content-type": "^1.0.5",
315
+ "debug": "^4.4.3",
316
+ "http-errors": "^2.0.0",
317
+ "iconv-lite": "^0.7.0",
318
+ "on-finished": "^2.4.1",
319
+ "qs": "^6.14.1",
320
+ "raw-body": "^3.0.1",
321
+ "type-is": "^2.0.1"
322
+ },
323
+ "engines": {
324
+ "node": ">=18"
325
+ },
326
+ "funding": {
327
+ "type": "opencollective",
328
+ "url": "https://opencollective.com/express"
329
+ }
330
+ },
331
+ "node_modules/buffer": {
332
+ "version": "5.7.1",
333
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
334
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
335
+ "funding": [
336
+ {
337
+ "type": "github",
338
+ "url": "https://github.com/sponsors/feross"
339
+ },
340
+ {
341
+ "type": "patreon",
342
+ "url": "https://www.patreon.com/feross"
343
+ },
344
+ {
345
+ "type": "consulting",
346
+ "url": "https://feross.org/support"
347
+ }
348
+ ],
349
+ "license": "MIT",
350
+ "dependencies": {
351
+ "base64-js": "^1.3.1",
352
+ "ieee754": "^1.1.13"
353
+ }
354
+ },
355
+ "node_modules/buffer-from": {
356
+ "version": "1.1.2",
357
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
358
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
359
+ "license": "MIT"
360
+ },
361
+ "node_modules/busboy": {
362
+ "version": "1.6.0",
363
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
364
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
365
+ "dependencies": {
366
+ "streamsearch": "^1.1.0"
367
+ },
368
+ "engines": {
369
+ "node": ">=10.16.0"
370
+ }
371
+ },
372
+ "node_modules/bytes": {
373
+ "version": "3.1.2",
374
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
375
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
376
+ "license": "MIT",
377
+ "engines": {
378
+ "node": ">= 0.8"
379
+ }
380
+ },
381
+ "node_modules/call-bind-apply-helpers": {
382
+ "version": "1.0.2",
383
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
384
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
385
+ "license": "MIT",
386
+ "dependencies": {
387
+ "es-errors": "^1.3.0",
388
+ "function-bind": "^1.1.2"
389
+ },
390
+ "engines": {
391
+ "node": ">= 0.4"
392
+ }
393
+ },
394
+ "node_modules/call-bound": {
395
+ "version": "1.0.4",
396
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
397
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
398
+ "license": "MIT",
399
+ "dependencies": {
400
+ "call-bind-apply-helpers": "^1.0.2",
401
+ "get-intrinsic": "^1.3.0"
402
+ },
403
+ "engines": {
404
+ "node": ">= 0.4"
405
+ },
406
+ "funding": {
407
+ "url": "https://github.com/sponsors/ljharb"
408
+ }
409
+ },
410
+ "node_modules/caseless": {
411
+ "version": "0.12.0",
412
+ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
413
+ "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==",
414
+ "license": "Apache-2.0"
415
+ },
416
+ "node_modules/chownr": {
417
+ "version": "1.1.4",
418
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
419
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
420
+ "license": "ISC"
421
+ },
422
+ "node_modules/color": {
423
+ "version": "4.2.3",
424
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
425
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
426
+ "license": "MIT",
427
+ "dependencies": {
428
+ "color-convert": "^2.0.1",
429
+ "color-string": "^1.9.0"
430
+ },
431
+ "engines": {
432
+ "node": ">=12.5.0"
433
+ }
434
+ },
435
+ "node_modules/color-convert": {
436
+ "version": "2.0.1",
437
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
438
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
439
+ "license": "MIT",
440
+ "dependencies": {
441
+ "color-name": "~1.1.4"
442
+ },
443
+ "engines": {
444
+ "node": ">=7.0.0"
445
+ }
446
+ },
447
+ "node_modules/color-name": {
448
+ "version": "1.1.4",
449
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
450
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
451
+ "license": "MIT"
452
+ },
453
+ "node_modules/color-string": {
454
+ "version": "1.9.1",
455
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
456
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
457
+ "license": "MIT",
458
+ "dependencies": {
459
+ "color-name": "^1.0.0",
460
+ "simple-swizzle": "^0.2.2"
461
+ }
462
+ },
463
+ "node_modules/compressible": {
464
+ "version": "2.0.18",
465
+ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
466
+ "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
467
+ "license": "MIT",
468
+ "dependencies": {
469
+ "mime-db": ">= 1.43.0 < 2"
470
+ },
471
+ "engines": {
472
+ "node": ">= 0.6"
473
+ }
474
+ },
475
+ "node_modules/compression": {
476
+ "version": "1.8.1",
477
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
478
+ "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
479
+ "license": "MIT",
480
+ "dependencies": {
481
+ "bytes": "3.1.2",
482
+ "compressible": "~2.0.18",
483
+ "debug": "2.6.9",
484
+ "negotiator": "~0.6.4",
485
+ "on-headers": "~1.1.0",
486
+ "safe-buffer": "5.2.1",
487
+ "vary": "~1.1.2"
488
+ },
489
+ "engines": {
490
+ "node": ">= 0.8.0"
491
+ }
492
+ },
493
+ "node_modules/compression/node_modules/debug": {
494
+ "version": "2.6.9",
495
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
496
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
497
+ "license": "MIT",
498
+ "dependencies": {
499
+ "ms": "2.0.0"
500
+ }
501
+ },
502
+ "node_modules/compression/node_modules/ms": {
503
+ "version": "2.0.0",
504
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
505
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
506
+ "license": "MIT"
507
+ },
508
+ "node_modules/compression/node_modules/negotiator": {
509
+ "version": "0.6.4",
510
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
511
+ "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
512
+ "license": "MIT",
513
+ "engines": {
514
+ "node": ">= 0.6"
515
+ }
516
+ },
517
+ "node_modules/concat-stream": {
518
+ "version": "2.0.0",
519
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
520
+ "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
521
+ "engines": [
522
+ "node >= 6.0"
523
+ ],
524
+ "license": "MIT",
525
+ "dependencies": {
526
+ "buffer-from": "^1.0.0",
527
+ "inherits": "^2.0.3",
528
+ "readable-stream": "^3.0.2",
529
+ "typedarray": "^0.0.6"
530
+ }
531
+ },
532
+ "node_modules/content-disposition": {
533
+ "version": "1.1.0",
534
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
535
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
536
+ "license": "MIT",
537
+ "engines": {
538
+ "node": ">=18"
539
+ },
540
+ "funding": {
541
+ "type": "opencollective",
542
+ "url": "https://opencollective.com/express"
543
+ }
544
+ },
545
+ "node_modules/content-type": {
546
+ "version": "1.0.5",
547
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
548
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
549
+ "license": "MIT",
550
+ "engines": {
551
+ "node": ">= 0.6"
552
+ }
553
+ },
554
+ "node_modules/cookie": {
555
+ "version": "0.7.2",
556
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
557
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
558
+ "license": "MIT",
559
+ "engines": {
560
+ "node": ">= 0.6"
561
+ }
562
+ },
563
+ "node_modules/cookie-signature": {
564
+ "version": "1.2.2",
565
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
566
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
567
+ "license": "MIT",
568
+ "engines": {
569
+ "node": ">=6.6.0"
570
+ }
571
+ },
572
+ "node_modules/debug": {
573
+ "version": "4.4.3",
574
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
575
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
576
+ "license": "MIT",
577
+ "dependencies": {
578
+ "ms": "^2.1.3"
579
+ },
580
+ "engines": {
581
+ "node": ">=6.0"
582
+ },
583
+ "peerDependenciesMeta": {
584
+ "supports-color": {
585
+ "optional": true
586
+ }
587
+ }
588
+ },
589
+ "node_modules/decompress-response": {
590
+ "version": "6.0.0",
591
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
592
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
593
+ "license": "MIT",
594
+ "dependencies": {
595
+ "mimic-response": "^3.1.0"
596
+ },
597
+ "engines": {
598
+ "node": ">=10"
599
+ },
600
+ "funding": {
601
+ "url": "https://github.com/sponsors/sindresorhus"
602
+ }
603
+ },
604
+ "node_modules/deep-extend": {
605
+ "version": "0.6.0",
606
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
607
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
608
+ "license": "MIT",
609
+ "engines": {
610
+ "node": ">=4.0.0"
611
+ }
612
+ },
613
+ "node_modules/depd": {
614
+ "version": "2.0.0",
615
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
616
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
617
+ "license": "MIT",
618
+ "engines": {
619
+ "node": ">= 0.8"
620
+ }
621
+ },
622
+ "node_modules/detect-libc": {
623
+ "version": "2.1.2",
624
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
625
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
626
+ "license": "Apache-2.0",
627
+ "engines": {
628
+ "node": ">=8"
629
+ }
630
+ },
631
+ "node_modules/dunder-proto": {
632
+ "version": "1.0.1",
633
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
634
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
635
+ "license": "MIT",
636
+ "dependencies": {
637
+ "call-bind-apply-helpers": "^1.0.1",
638
+ "es-errors": "^1.3.0",
639
+ "gopd": "^1.2.0"
640
+ },
641
+ "engines": {
642
+ "node": ">= 0.4"
643
+ }
644
+ },
645
+ "node_modules/ee-first": {
646
+ "version": "1.1.1",
647
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
648
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
649
+ "license": "MIT"
650
+ },
651
+ "node_modules/encodeurl": {
652
+ "version": "2.0.0",
653
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
654
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
655
+ "license": "MIT",
656
+ "engines": {
657
+ "node": ">= 0.8"
658
+ }
659
+ },
660
+ "node_modules/end-of-stream": {
661
+ "version": "1.4.5",
662
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
663
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
664
+ "license": "MIT",
665
+ "dependencies": {
666
+ "once": "^1.4.0"
667
+ }
668
+ },
669
+ "node_modules/env-paths": {
670
+ "version": "2.2.1",
671
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
672
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
673
+ "license": "MIT",
674
+ "engines": {
675
+ "node": ">=6"
676
+ }
677
+ },
678
+ "node_modules/es-define-property": {
679
+ "version": "1.0.1",
680
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
681
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
682
+ "license": "MIT",
683
+ "engines": {
684
+ "node": ">= 0.4"
685
+ }
686
+ },
687
+ "node_modules/es-errors": {
688
+ "version": "1.3.0",
689
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
690
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
691
+ "license": "MIT",
692
+ "engines": {
693
+ "node": ">= 0.4"
694
+ }
695
+ },
696
+ "node_modules/es-object-atoms": {
697
+ "version": "1.1.2",
698
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
699
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
700
+ "license": "MIT",
701
+ "dependencies": {
702
+ "es-errors": "^1.3.0"
703
+ },
704
+ "engines": {
705
+ "node": ">= 0.4"
706
+ }
707
+ },
708
+ "node_modules/escape-html": {
709
+ "version": "1.0.3",
710
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
711
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
712
+ "license": "MIT"
713
+ },
714
+ "node_modules/etag": {
715
+ "version": "1.8.1",
716
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
717
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
718
+ "license": "MIT",
719
+ "engines": {
720
+ "node": ">= 0.6"
721
+ }
722
+ },
723
+ "node_modules/events-universal": {
724
+ "version": "1.0.1",
725
+ "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
726
+ "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
727
+ "license": "Apache-2.0",
728
+ "dependencies": {
729
+ "bare-events": "^2.7.0"
730
+ }
731
+ },
732
+ "node_modules/expand-template": {
733
+ "version": "2.0.3",
734
+ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
735
+ "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
736
+ "license": "(MIT OR WTFPL)",
737
+ "engines": {
738
+ "node": ">=6"
739
+ }
740
+ },
741
+ "node_modules/express": {
742
+ "version": "5.2.1",
743
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
744
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
745
+ "license": "MIT",
746
+ "dependencies": {
747
+ "accepts": "^2.0.0",
748
+ "body-parser": "^2.2.1",
749
+ "content-disposition": "^1.0.0",
750
+ "content-type": "^1.0.5",
751
+ "cookie": "^0.7.1",
752
+ "cookie-signature": "^1.2.1",
753
+ "debug": "^4.4.0",
754
+ "depd": "^2.0.0",
755
+ "encodeurl": "^2.0.0",
756
+ "escape-html": "^1.0.3",
757
+ "etag": "^1.8.1",
758
+ "finalhandler": "^2.1.0",
759
+ "fresh": "^2.0.0",
760
+ "http-errors": "^2.0.0",
761
+ "merge-descriptors": "^2.0.0",
762
+ "mime-types": "^3.0.0",
763
+ "on-finished": "^2.4.1",
764
+ "once": "^1.4.0",
765
+ "parseurl": "^1.3.3",
766
+ "proxy-addr": "^2.0.7",
767
+ "qs": "^6.14.0",
768
+ "range-parser": "^1.2.1",
769
+ "router": "^2.2.0",
770
+ "send": "^1.1.0",
771
+ "serve-static": "^2.2.0",
772
+ "statuses": "^2.0.1",
773
+ "type-is": "^2.0.1",
774
+ "vary": "^1.1.2"
775
+ },
776
+ "engines": {
777
+ "node": ">= 18"
778
+ },
779
+ "funding": {
780
+ "type": "opencollective",
781
+ "url": "https://opencollective.com/express"
782
+ }
783
+ },
784
+ "node_modules/fast-fifo": {
785
+ "version": "1.3.2",
786
+ "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
787
+ "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
788
+ "license": "MIT"
789
+ },
790
+ "node_modules/ffmpeg-static": {
791
+ "version": "5.3.0",
792
+ "resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.3.0.tgz",
793
+ "integrity": "sha512-H+K6sW6TiIX6VGend0KQwthe+kaceeH/luE8dIZyOP35ik7ahYojDuqlTV1bOrtEwl01sy2HFNGQfi5IDJvotg==",
794
+ "hasInstallScript": true,
795
+ "license": "GPL-3.0-or-later",
796
+ "dependencies": {
797
+ "@derhuerst/http-basic": "^8.2.0",
798
+ "env-paths": "^2.2.0",
799
+ "https-proxy-agent": "^5.0.0",
800
+ "progress": "^2.0.3"
801
+ },
802
+ "engines": {
803
+ "node": ">=16"
804
+ }
805
+ },
806
+ "node_modules/finalhandler": {
807
+ "version": "2.1.1",
808
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
809
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
810
+ "license": "MIT",
811
+ "dependencies": {
812
+ "debug": "^4.4.0",
813
+ "encodeurl": "^2.0.0",
814
+ "escape-html": "^1.0.3",
815
+ "on-finished": "^2.4.1",
816
+ "parseurl": "^1.3.3",
817
+ "statuses": "^2.0.1"
818
+ },
819
+ "engines": {
820
+ "node": ">= 18.0.0"
821
+ },
822
+ "funding": {
823
+ "type": "opencollective",
824
+ "url": "https://opencollective.com/express"
825
+ }
826
+ },
827
+ "node_modules/flatbuffers": {
828
+ "version": "1.12.0",
829
+ "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-1.12.0.tgz",
830
+ "integrity": "sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==",
831
+ "license": "SEE LICENSE IN LICENSE.txt"
832
+ },
833
+ "node_modules/fluent-ffmpeg": {
834
+ "version": "2.1.3",
835
+ "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz",
836
+ "integrity": "sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==",
837
+ "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
838
+ "license": "MIT",
839
+ "dependencies": {
840
+ "async": "^0.2.9",
841
+ "which": "^1.1.1"
842
+ },
843
+ "engines": {
844
+ "node": ">=18"
845
+ }
846
+ },
847
+ "node_modules/forwarded": {
848
+ "version": "0.2.0",
849
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
850
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
851
+ "license": "MIT",
852
+ "engines": {
853
+ "node": ">= 0.6"
854
+ }
855
+ },
856
+ "node_modules/fresh": {
857
+ "version": "2.0.0",
858
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
859
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
860
+ "license": "MIT",
861
+ "engines": {
862
+ "node": ">= 0.8"
863
+ }
864
+ },
865
+ "node_modules/fs-constants": {
866
+ "version": "1.0.0",
867
+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
868
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
869
+ "license": "MIT"
870
+ },
871
+ "node_modules/function-bind": {
872
+ "version": "1.1.2",
873
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
874
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
875
+ "license": "MIT",
876
+ "funding": {
877
+ "url": "https://github.com/sponsors/ljharb"
878
+ }
879
+ },
880
+ "node_modules/get-intrinsic": {
881
+ "version": "1.3.0",
882
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
883
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
884
+ "license": "MIT",
885
+ "dependencies": {
886
+ "call-bind-apply-helpers": "^1.0.2",
887
+ "es-define-property": "^1.0.1",
888
+ "es-errors": "^1.3.0",
889
+ "es-object-atoms": "^1.1.1",
890
+ "function-bind": "^1.1.2",
891
+ "get-proto": "^1.0.1",
892
+ "gopd": "^1.2.0",
893
+ "has-symbols": "^1.1.0",
894
+ "hasown": "^2.0.2",
895
+ "math-intrinsics": "^1.1.0"
896
+ },
897
+ "engines": {
898
+ "node": ">= 0.4"
899
+ },
900
+ "funding": {
901
+ "url": "https://github.com/sponsors/ljharb"
902
+ }
903
+ },
904
+ "node_modules/get-proto": {
905
+ "version": "1.0.1",
906
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
907
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
908
+ "license": "MIT",
909
+ "dependencies": {
910
+ "dunder-proto": "^1.0.1",
911
+ "es-object-atoms": "^1.0.0"
912
+ },
913
+ "engines": {
914
+ "node": ">= 0.4"
915
+ }
916
+ },
917
+ "node_modules/github-from-package": {
918
+ "version": "0.0.0",
919
+ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
920
+ "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
921
+ "license": "MIT"
922
+ },
923
+ "node_modules/gopd": {
924
+ "version": "1.2.0",
925
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
926
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
927
+ "license": "MIT",
928
+ "engines": {
929
+ "node": ">= 0.4"
930
+ },
931
+ "funding": {
932
+ "url": "https://github.com/sponsors/ljharb"
933
+ }
934
+ },
935
+ "node_modules/guid-typescript": {
936
+ "version": "1.0.9",
937
+ "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz",
938
+ "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==",
939
+ "license": "ISC"
940
+ },
941
+ "node_modules/has-symbols": {
942
+ "version": "1.1.0",
943
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
944
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
945
+ "license": "MIT",
946
+ "engines": {
947
+ "node": ">= 0.4"
948
+ },
949
+ "funding": {
950
+ "url": "https://github.com/sponsors/ljharb"
951
+ }
952
+ },
953
+ "node_modules/hasown": {
954
+ "version": "2.0.4",
955
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
956
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
957
+ "license": "MIT",
958
+ "dependencies": {
959
+ "function-bind": "^1.1.2"
960
+ },
961
+ "engines": {
962
+ "node": ">= 0.4"
963
+ }
964
+ },
965
+ "node_modules/helmet": {
966
+ "version": "8.2.0",
967
+ "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.2.0.tgz",
968
+ "integrity": "sha512-DRgTIUgnWcJ62KyarxxziuqYxKGnR6Rgg19BlbucN/dpmJbl1XOit6qvoOX0ZT+HhWe5OUVhU/a1zpGyc1xA0Q==",
969
+ "license": "MIT",
970
+ "engines": {
971
+ "node": ">=18.0.0"
972
+ },
973
+ "funding": {
974
+ "url": "https://github.com/sponsors/EvanHahn"
975
+ }
976
+ },
977
+ "node_modules/http-errors": {
978
+ "version": "2.0.1",
979
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
980
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
981
+ "license": "MIT",
982
+ "dependencies": {
983
+ "depd": "~2.0.0",
984
+ "inherits": "~2.0.4",
985
+ "setprototypeof": "~1.2.0",
986
+ "statuses": "~2.0.2",
987
+ "toidentifier": "~1.0.1"
988
+ },
989
+ "engines": {
990
+ "node": ">= 0.8"
991
+ },
992
+ "funding": {
993
+ "type": "opencollective",
994
+ "url": "https://opencollective.com/express"
995
+ }
996
+ },
997
+ "node_modules/http-response-object": {
998
+ "version": "3.0.2",
999
+ "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz",
1000
+ "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==",
1001
+ "license": "MIT",
1002
+ "dependencies": {
1003
+ "@types/node": "^10.0.3"
1004
+ }
1005
+ },
1006
+ "node_modules/https-proxy-agent": {
1007
+ "version": "5.0.1",
1008
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
1009
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
1010
+ "license": "MIT",
1011
+ "dependencies": {
1012
+ "agent-base": "6",
1013
+ "debug": "4"
1014
+ },
1015
+ "engines": {
1016
+ "node": ">= 6"
1017
+ }
1018
+ },
1019
+ "node_modules/iconv-lite": {
1020
+ "version": "0.7.2",
1021
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
1022
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
1023
+ "license": "MIT",
1024
+ "dependencies": {
1025
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
1026
+ },
1027
+ "engines": {
1028
+ "node": ">=0.10.0"
1029
+ },
1030
+ "funding": {
1031
+ "type": "opencollective",
1032
+ "url": "https://opencollective.com/express"
1033
+ }
1034
+ },
1035
+ "node_modules/ieee754": {
1036
+ "version": "1.2.1",
1037
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
1038
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
1039
+ "funding": [
1040
+ {
1041
+ "type": "github",
1042
+ "url": "https://github.com/sponsors/feross"
1043
+ },
1044
+ {
1045
+ "type": "patreon",
1046
+ "url": "https://www.patreon.com/feross"
1047
+ },
1048
+ {
1049
+ "type": "consulting",
1050
+ "url": "https://feross.org/support"
1051
+ }
1052
+ ],
1053
+ "license": "BSD-3-Clause"
1054
+ },
1055
+ "node_modules/inherits": {
1056
+ "version": "2.0.4",
1057
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
1058
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
1059
+ "license": "ISC"
1060
+ },
1061
+ "node_modules/ini": {
1062
+ "version": "1.3.8",
1063
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
1064
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
1065
+ "license": "ISC"
1066
+ },
1067
+ "node_modules/ipaddr.js": {
1068
+ "version": "1.9.1",
1069
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
1070
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
1071
+ "license": "MIT",
1072
+ "engines": {
1073
+ "node": ">= 0.10"
1074
+ }
1075
+ },
1076
+ "node_modules/is-arrayish": {
1077
+ "version": "0.3.4",
1078
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
1079
+ "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
1080
+ "license": "MIT"
1081
+ },
1082
+ "node_modules/is-promise": {
1083
+ "version": "4.0.0",
1084
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
1085
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
1086
+ "license": "MIT"
1087
+ },
1088
+ "node_modules/isexe": {
1089
+ "version": "2.0.0",
1090
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
1091
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
1092
+ "license": "ISC"
1093
+ },
1094
+ "node_modules/long": {
1095
+ "version": "4.0.0",
1096
+ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
1097
+ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
1098
+ "license": "Apache-2.0"
1099
+ },
1100
+ "node_modules/math-intrinsics": {
1101
+ "version": "1.1.0",
1102
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
1103
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
1104
+ "license": "MIT",
1105
+ "engines": {
1106
+ "node": ">= 0.4"
1107
+ }
1108
+ },
1109
+ "node_modules/media-typer": {
1110
+ "version": "1.1.0",
1111
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
1112
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
1113
+ "license": "MIT",
1114
+ "engines": {
1115
+ "node": ">= 0.8"
1116
+ }
1117
+ },
1118
+ "node_modules/merge-descriptors": {
1119
+ "version": "2.0.0",
1120
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
1121
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
1122
+ "license": "MIT",
1123
+ "engines": {
1124
+ "node": ">=18"
1125
+ },
1126
+ "funding": {
1127
+ "url": "https://github.com/sponsors/sindresorhus"
1128
+ }
1129
+ },
1130
+ "node_modules/mime-db": {
1131
+ "version": "1.54.0",
1132
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
1133
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
1134
+ "license": "MIT",
1135
+ "engines": {
1136
+ "node": ">= 0.6"
1137
+ }
1138
+ },
1139
+ "node_modules/mime-types": {
1140
+ "version": "3.0.2",
1141
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
1142
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
1143
+ "license": "MIT",
1144
+ "dependencies": {
1145
+ "mime-db": "^1.54.0"
1146
+ },
1147
+ "engines": {
1148
+ "node": ">=18"
1149
+ },
1150
+ "funding": {
1151
+ "type": "opencollective",
1152
+ "url": "https://opencollective.com/express"
1153
+ }
1154
+ },
1155
+ "node_modules/mimic-response": {
1156
+ "version": "3.1.0",
1157
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
1158
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
1159
+ "license": "MIT",
1160
+ "engines": {
1161
+ "node": ">=10"
1162
+ },
1163
+ "funding": {
1164
+ "url": "https://github.com/sponsors/sindresorhus"
1165
+ }
1166
+ },
1167
+ "node_modules/minimist": {
1168
+ "version": "1.2.8",
1169
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
1170
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
1171
+ "license": "MIT",
1172
+ "funding": {
1173
+ "url": "https://github.com/sponsors/ljharb"
1174
+ }
1175
+ },
1176
+ "node_modules/mkdirp-classic": {
1177
+ "version": "0.5.3",
1178
+ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
1179
+ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
1180
+ "license": "MIT"
1181
+ },
1182
+ "node_modules/ms": {
1183
+ "version": "2.1.3",
1184
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1185
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1186
+ "license": "MIT"
1187
+ },
1188
+ "node_modules/multer": {
1189
+ "version": "2.1.1",
1190
+ "resolved": "https://registry.npmjs.org/multer/-/multer-2.1.1.tgz",
1191
+ "integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==",
1192
+ "license": "MIT",
1193
+ "dependencies": {
1194
+ "append-field": "^1.0.0",
1195
+ "busboy": "^1.6.0",
1196
+ "concat-stream": "^2.0.0",
1197
+ "type-is": "^1.6.18"
1198
+ },
1199
+ "engines": {
1200
+ "node": ">= 10.16.0"
1201
+ },
1202
+ "funding": {
1203
+ "type": "opencollective",
1204
+ "url": "https://opencollective.com/express"
1205
+ }
1206
+ },
1207
+ "node_modules/multer/node_modules/media-typer": {
1208
+ "version": "0.3.0",
1209
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
1210
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
1211
+ "license": "MIT",
1212
+ "engines": {
1213
+ "node": ">= 0.6"
1214
+ }
1215
+ },
1216
+ "node_modules/multer/node_modules/mime-db": {
1217
+ "version": "1.52.0",
1218
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
1219
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
1220
+ "license": "MIT",
1221
+ "engines": {
1222
+ "node": ">= 0.6"
1223
+ }
1224
+ },
1225
+ "node_modules/multer/node_modules/mime-types": {
1226
+ "version": "2.1.35",
1227
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
1228
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
1229
+ "license": "MIT",
1230
+ "dependencies": {
1231
+ "mime-db": "1.52.0"
1232
+ },
1233
+ "engines": {
1234
+ "node": ">= 0.6"
1235
+ }
1236
+ },
1237
+ "node_modules/multer/node_modules/type-is": {
1238
+ "version": "1.6.18",
1239
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1240
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1241
+ "license": "MIT",
1242
+ "dependencies": {
1243
+ "media-typer": "0.3.0",
1244
+ "mime-types": "~2.1.24"
1245
+ },
1246
+ "engines": {
1247
+ "node": ">= 0.6"
1248
+ }
1249
+ },
1250
+ "node_modules/napi-build-utils": {
1251
+ "version": "2.0.0",
1252
+ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
1253
+ "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
1254
+ "license": "MIT"
1255
+ },
1256
+ "node_modules/negotiator": {
1257
+ "version": "1.0.0",
1258
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
1259
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
1260
+ "license": "MIT",
1261
+ "engines": {
1262
+ "node": ">= 0.6"
1263
+ }
1264
+ },
1265
+ "node_modules/node-abi": {
1266
+ "version": "3.92.0",
1267
+ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz",
1268
+ "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==",
1269
+ "license": "MIT",
1270
+ "dependencies": {
1271
+ "semver": "^7.3.5"
1272
+ },
1273
+ "engines": {
1274
+ "node": ">=10"
1275
+ }
1276
+ },
1277
+ "node_modules/node-addon-api": {
1278
+ "version": "6.1.0",
1279
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
1280
+ "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==",
1281
+ "license": "MIT"
1282
+ },
1283
+ "node_modules/object-inspect": {
1284
+ "version": "1.13.4",
1285
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
1286
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
1287
+ "license": "MIT",
1288
+ "engines": {
1289
+ "node": ">= 0.4"
1290
+ },
1291
+ "funding": {
1292
+ "url": "https://github.com/sponsors/ljharb"
1293
+ }
1294
+ },
1295
+ "node_modules/on-finished": {
1296
+ "version": "2.4.1",
1297
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
1298
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
1299
+ "license": "MIT",
1300
+ "dependencies": {
1301
+ "ee-first": "1.1.1"
1302
+ },
1303
+ "engines": {
1304
+ "node": ">= 0.8"
1305
+ }
1306
+ },
1307
+ "node_modules/on-headers": {
1308
+ "version": "1.1.0",
1309
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
1310
+ "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
1311
+ "license": "MIT",
1312
+ "engines": {
1313
+ "node": ">= 0.8"
1314
+ }
1315
+ },
1316
+ "node_modules/once": {
1317
+ "version": "1.4.0",
1318
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
1319
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
1320
+ "license": "ISC",
1321
+ "dependencies": {
1322
+ "wrappy": "1"
1323
+ }
1324
+ },
1325
+ "node_modules/onnx-proto": {
1326
+ "version": "4.0.4",
1327
+ "resolved": "https://registry.npmjs.org/onnx-proto/-/onnx-proto-4.0.4.tgz",
1328
+ "integrity": "sha512-aldMOB3HRoo6q/phyB6QRQxSt895HNNw82BNyZ2CMh4bjeKv7g/c+VpAFtJuEMVfYLMbRx61hbuqnKceLeDcDA==",
1329
+ "license": "MIT",
1330
+ "dependencies": {
1331
+ "protobufjs": "^6.8.8"
1332
+ }
1333
+ },
1334
+ "node_modules/onnxruntime-common": {
1335
+ "version": "1.14.0",
1336
+ "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.14.0.tgz",
1337
+ "integrity": "sha512-3LJpegM2iMNRX2wUmtYfeX/ytfOzNwAWKSq1HbRrKc9+uqG/FsEA0bbKZl1btQeZaXhC26l44NWpNUeXPII7Ew==",
1338
+ "license": "MIT"
1339
+ },
1340
+ "node_modules/onnxruntime-node": {
1341
+ "version": "1.14.0",
1342
+ "resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.14.0.tgz",
1343
+ "integrity": "sha512-5ba7TWomIV/9b6NH/1x/8QEeowsb+jBEvFzU6z0T4mNsFwdPqXeFUM7uxC6QeSRkEbWu3qEB0VMjrvzN/0S9+w==",
1344
+ "license": "MIT",
1345
+ "optional": true,
1346
+ "os": [
1347
+ "win32",
1348
+ "darwin",
1349
+ "linux"
1350
+ ],
1351
+ "dependencies": {
1352
+ "onnxruntime-common": "~1.14.0"
1353
+ }
1354
+ },
1355
+ "node_modules/onnxruntime-web": {
1356
+ "version": "1.14.0",
1357
+ "resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.14.0.tgz",
1358
+ "integrity": "sha512-Kcqf43UMfW8mCydVGcX9OMXI2VN17c0p6XvR7IPSZzBf/6lteBzXHvcEVWDPmCKuGombl997HgLqj91F11DzXw==",
1359
+ "license": "MIT",
1360
+ "dependencies": {
1361
+ "flatbuffers": "^1.12.0",
1362
+ "guid-typescript": "^1.0.9",
1363
+ "long": "^4.0.0",
1364
+ "onnx-proto": "^4.0.4",
1365
+ "onnxruntime-common": "~1.14.0",
1366
+ "platform": "^1.3.6"
1367
+ }
1368
+ },
1369
+ "node_modules/parse-cache-control": {
1370
+ "version": "1.0.1",
1371
+ "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz",
1372
+ "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg=="
1373
+ },
1374
+ "node_modules/parseurl": {
1375
+ "version": "1.3.3",
1376
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
1377
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
1378
+ "license": "MIT",
1379
+ "engines": {
1380
+ "node": ">= 0.8"
1381
+ }
1382
+ },
1383
+ "node_modules/path-to-regexp": {
1384
+ "version": "8.4.2",
1385
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
1386
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
1387
+ "license": "MIT",
1388
+ "funding": {
1389
+ "type": "opencollective",
1390
+ "url": "https://opencollective.com/express"
1391
+ }
1392
+ },
1393
+ "node_modules/platform": {
1394
+ "version": "1.3.6",
1395
+ "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
1396
+ "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
1397
+ "license": "MIT"
1398
+ },
1399
+ "node_modules/prebuild-install": {
1400
+ "version": "7.1.3",
1401
+ "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
1402
+ "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
1403
+ "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
1404
+ "license": "MIT",
1405
+ "dependencies": {
1406
+ "detect-libc": "^2.0.0",
1407
+ "expand-template": "^2.0.3",
1408
+ "github-from-package": "0.0.0",
1409
+ "minimist": "^1.2.3",
1410
+ "mkdirp-classic": "^0.5.3",
1411
+ "napi-build-utils": "^2.0.0",
1412
+ "node-abi": "^3.3.0",
1413
+ "pump": "^3.0.0",
1414
+ "rc": "^1.2.7",
1415
+ "simple-get": "^4.0.0",
1416
+ "tar-fs": "^2.0.0",
1417
+ "tunnel-agent": "^0.6.0"
1418
+ },
1419
+ "bin": {
1420
+ "prebuild-install": "bin.js"
1421
+ },
1422
+ "engines": {
1423
+ "node": ">=10"
1424
+ }
1425
+ },
1426
+ "node_modules/prebuild-install/node_modules/tar-fs": {
1427
+ "version": "2.1.4",
1428
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
1429
+ "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
1430
+ "license": "MIT",
1431
+ "dependencies": {
1432
+ "chownr": "^1.1.1",
1433
+ "mkdirp-classic": "^0.5.2",
1434
+ "pump": "^3.0.0",
1435
+ "tar-stream": "^2.1.4"
1436
+ }
1437
+ },
1438
+ "node_modules/prebuild-install/node_modules/tar-stream": {
1439
+ "version": "2.2.0",
1440
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
1441
+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
1442
+ "license": "MIT",
1443
+ "dependencies": {
1444
+ "bl": "^4.0.3",
1445
+ "end-of-stream": "^1.4.1",
1446
+ "fs-constants": "^1.0.0",
1447
+ "inherits": "^2.0.3",
1448
+ "readable-stream": "^3.1.1"
1449
+ },
1450
+ "engines": {
1451
+ "node": ">=6"
1452
+ }
1453
+ },
1454
+ "node_modules/progress": {
1455
+ "version": "2.0.3",
1456
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
1457
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
1458
+ "license": "MIT",
1459
+ "engines": {
1460
+ "node": ">=0.4.0"
1461
+ }
1462
+ },
1463
+ "node_modules/protobufjs": {
1464
+ "version": "6.11.6",
1465
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.6.tgz",
1466
+ "integrity": "sha512-k8BHqgPBOtrlougZZqF2uUk5Z7bN8f0wj+3e8M3hvtSv0NBAz4VBy5f6R5Nxq/l+i7mRFTgNZb2trxqTpHNY/A==",
1467
+ "hasInstallScript": true,
1468
+ "license": "BSD-3-Clause",
1469
+ "dependencies": {
1470
+ "@protobufjs/aspromise": "^1.1.2",
1471
+ "@protobufjs/base64": "^1.1.2",
1472
+ "@protobufjs/codegen": "^2.0.4",
1473
+ "@protobufjs/eventemitter": "^1.1.0",
1474
+ "@protobufjs/fetch": "^1.1.0",
1475
+ "@protobufjs/float": "^1.0.2",
1476
+ "@protobufjs/inquire": "^1.1.0",
1477
+ "@protobufjs/path": "^1.1.2",
1478
+ "@protobufjs/pool": "^1.1.0",
1479
+ "@protobufjs/utf8": "^1.1.0",
1480
+ "@types/long": "^4.0.1",
1481
+ "@types/node": ">=13.7.0",
1482
+ "long": "^4.0.0"
1483
+ },
1484
+ "bin": {
1485
+ "pbjs": "bin/pbjs",
1486
+ "pbts": "bin/pbts"
1487
+ }
1488
+ },
1489
+ "node_modules/protobufjs/node_modules/@types/node": {
1490
+ "version": "25.9.1",
1491
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz",
1492
+ "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
1493
+ "license": "MIT",
1494
+ "dependencies": {
1495
+ "undici-types": ">=7.24.0 <7.24.7"
1496
+ }
1497
+ },
1498
+ "node_modules/proxy-addr": {
1499
+ "version": "2.0.7",
1500
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
1501
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
1502
+ "license": "MIT",
1503
+ "dependencies": {
1504
+ "forwarded": "0.2.0",
1505
+ "ipaddr.js": "1.9.1"
1506
+ },
1507
+ "engines": {
1508
+ "node": ">= 0.10"
1509
+ }
1510
+ },
1511
+ "node_modules/pump": {
1512
+ "version": "3.0.4",
1513
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
1514
+ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
1515
+ "license": "MIT",
1516
+ "dependencies": {
1517
+ "end-of-stream": "^1.1.0",
1518
+ "once": "^1.3.1"
1519
+ }
1520
+ },
1521
+ "node_modules/qs": {
1522
+ "version": "6.15.2",
1523
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
1524
+ "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
1525
+ "license": "BSD-3-Clause",
1526
+ "dependencies": {
1527
+ "side-channel": "^1.1.0"
1528
+ },
1529
+ "engines": {
1530
+ "node": ">=0.6"
1531
+ },
1532
+ "funding": {
1533
+ "url": "https://github.com/sponsors/ljharb"
1534
+ }
1535
+ },
1536
+ "node_modules/range-parser": {
1537
+ "version": "1.2.1",
1538
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
1539
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
1540
+ "license": "MIT",
1541
+ "engines": {
1542
+ "node": ">= 0.6"
1543
+ }
1544
+ },
1545
+ "node_modules/raw-body": {
1546
+ "version": "3.0.2",
1547
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
1548
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
1549
+ "license": "MIT",
1550
+ "dependencies": {
1551
+ "bytes": "~3.1.2",
1552
+ "http-errors": "~2.0.1",
1553
+ "iconv-lite": "~0.7.0",
1554
+ "unpipe": "~1.0.0"
1555
+ },
1556
+ "engines": {
1557
+ "node": ">= 0.10"
1558
+ }
1559
+ },
1560
+ "node_modules/rc": {
1561
+ "version": "1.2.8",
1562
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
1563
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
1564
+ "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
1565
+ "dependencies": {
1566
+ "deep-extend": "^0.6.0",
1567
+ "ini": "~1.3.0",
1568
+ "minimist": "^1.2.0",
1569
+ "strip-json-comments": "~2.0.1"
1570
+ },
1571
+ "bin": {
1572
+ "rc": "cli.js"
1573
+ }
1574
+ },
1575
+ "node_modules/readable-stream": {
1576
+ "version": "3.6.2",
1577
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
1578
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
1579
+ "license": "MIT",
1580
+ "dependencies": {
1581
+ "inherits": "^2.0.3",
1582
+ "string_decoder": "^1.1.1",
1583
+ "util-deprecate": "^1.0.1"
1584
+ },
1585
+ "engines": {
1586
+ "node": ">= 6"
1587
+ }
1588
+ },
1589
+ "node_modules/router": {
1590
+ "version": "2.2.0",
1591
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
1592
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
1593
+ "license": "MIT",
1594
+ "dependencies": {
1595
+ "debug": "^4.4.0",
1596
+ "depd": "^2.0.0",
1597
+ "is-promise": "^4.0.0",
1598
+ "parseurl": "^1.3.3",
1599
+ "path-to-regexp": "^8.0.0"
1600
+ },
1601
+ "engines": {
1602
+ "node": ">= 18"
1603
+ }
1604
+ },
1605
+ "node_modules/safe-buffer": {
1606
+ "version": "5.2.1",
1607
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1608
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1609
+ "funding": [
1610
+ {
1611
+ "type": "github",
1612
+ "url": "https://github.com/sponsors/feross"
1613
+ },
1614
+ {
1615
+ "type": "patreon",
1616
+ "url": "https://www.patreon.com/feross"
1617
+ },
1618
+ {
1619
+ "type": "consulting",
1620
+ "url": "https://feross.org/support"
1621
+ }
1622
+ ],
1623
+ "license": "MIT"
1624
+ },
1625
+ "node_modules/safer-buffer": {
1626
+ "version": "2.1.2",
1627
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1628
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1629
+ "license": "MIT"
1630
+ },
1631
+ "node_modules/semver": {
1632
+ "version": "7.8.1",
1633
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
1634
+ "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
1635
+ "license": "ISC",
1636
+ "bin": {
1637
+ "semver": "bin/semver.js"
1638
+ },
1639
+ "engines": {
1640
+ "node": ">=10"
1641
+ }
1642
+ },
1643
+ "node_modules/send": {
1644
+ "version": "1.2.1",
1645
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
1646
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
1647
+ "license": "MIT",
1648
+ "dependencies": {
1649
+ "debug": "^4.4.3",
1650
+ "encodeurl": "^2.0.0",
1651
+ "escape-html": "^1.0.3",
1652
+ "etag": "^1.8.1",
1653
+ "fresh": "^2.0.0",
1654
+ "http-errors": "^2.0.1",
1655
+ "mime-types": "^3.0.2",
1656
+ "ms": "^2.1.3",
1657
+ "on-finished": "^2.4.1",
1658
+ "range-parser": "^1.2.1",
1659
+ "statuses": "^2.0.2"
1660
+ },
1661
+ "engines": {
1662
+ "node": ">= 18"
1663
+ },
1664
+ "funding": {
1665
+ "type": "opencollective",
1666
+ "url": "https://opencollective.com/express"
1667
+ }
1668
+ },
1669
+ "node_modules/serve-static": {
1670
+ "version": "2.2.1",
1671
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
1672
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
1673
+ "license": "MIT",
1674
+ "dependencies": {
1675
+ "encodeurl": "^2.0.0",
1676
+ "escape-html": "^1.0.3",
1677
+ "parseurl": "^1.3.3",
1678
+ "send": "^1.2.0"
1679
+ },
1680
+ "engines": {
1681
+ "node": ">= 18"
1682
+ },
1683
+ "funding": {
1684
+ "type": "opencollective",
1685
+ "url": "https://opencollective.com/express"
1686
+ }
1687
+ },
1688
+ "node_modules/setprototypeof": {
1689
+ "version": "1.2.0",
1690
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1691
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
1692
+ "license": "ISC"
1693
+ },
1694
+ "node_modules/sharp": {
1695
+ "version": "0.32.6",
1696
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz",
1697
+ "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==",
1698
+ "hasInstallScript": true,
1699
+ "license": "Apache-2.0",
1700
+ "dependencies": {
1701
+ "color": "^4.2.3",
1702
+ "detect-libc": "^2.0.2",
1703
+ "node-addon-api": "^6.1.0",
1704
+ "prebuild-install": "^7.1.1",
1705
+ "semver": "^7.5.4",
1706
+ "simple-get": "^4.0.1",
1707
+ "tar-fs": "^3.0.4",
1708
+ "tunnel-agent": "^0.6.0"
1709
+ },
1710
+ "engines": {
1711
+ "node": ">=14.15.0"
1712
+ },
1713
+ "funding": {
1714
+ "url": "https://opencollective.com/libvips"
1715
+ }
1716
+ },
1717
+ "node_modules/side-channel": {
1718
+ "version": "1.1.0",
1719
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
1720
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
1721
+ "license": "MIT",
1722
+ "dependencies": {
1723
+ "es-errors": "^1.3.0",
1724
+ "object-inspect": "^1.13.3",
1725
+ "side-channel-list": "^1.0.0",
1726
+ "side-channel-map": "^1.0.1",
1727
+ "side-channel-weakmap": "^1.0.2"
1728
+ },
1729
+ "engines": {
1730
+ "node": ">= 0.4"
1731
+ },
1732
+ "funding": {
1733
+ "url": "https://github.com/sponsors/ljharb"
1734
+ }
1735
+ },
1736
+ "node_modules/side-channel-list": {
1737
+ "version": "1.0.1",
1738
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
1739
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
1740
+ "license": "MIT",
1741
+ "dependencies": {
1742
+ "es-errors": "^1.3.0",
1743
+ "object-inspect": "^1.13.4"
1744
+ },
1745
+ "engines": {
1746
+ "node": ">= 0.4"
1747
+ },
1748
+ "funding": {
1749
+ "url": "https://github.com/sponsors/ljharb"
1750
+ }
1751
+ },
1752
+ "node_modules/side-channel-map": {
1753
+ "version": "1.0.1",
1754
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
1755
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
1756
+ "license": "MIT",
1757
+ "dependencies": {
1758
+ "call-bound": "^1.0.2",
1759
+ "es-errors": "^1.3.0",
1760
+ "get-intrinsic": "^1.2.5",
1761
+ "object-inspect": "^1.13.3"
1762
+ },
1763
+ "engines": {
1764
+ "node": ">= 0.4"
1765
+ },
1766
+ "funding": {
1767
+ "url": "https://github.com/sponsors/ljharb"
1768
+ }
1769
+ },
1770
+ "node_modules/side-channel-weakmap": {
1771
+ "version": "1.0.2",
1772
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
1773
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
1774
+ "license": "MIT",
1775
+ "dependencies": {
1776
+ "call-bound": "^1.0.2",
1777
+ "es-errors": "^1.3.0",
1778
+ "get-intrinsic": "^1.2.5",
1779
+ "object-inspect": "^1.13.3",
1780
+ "side-channel-map": "^1.0.1"
1781
+ },
1782
+ "engines": {
1783
+ "node": ">= 0.4"
1784
+ },
1785
+ "funding": {
1786
+ "url": "https://github.com/sponsors/ljharb"
1787
+ }
1788
+ },
1789
+ "node_modules/simple-concat": {
1790
+ "version": "1.0.1",
1791
+ "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
1792
+ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
1793
+ "funding": [
1794
+ {
1795
+ "type": "github",
1796
+ "url": "https://github.com/sponsors/feross"
1797
+ },
1798
+ {
1799
+ "type": "patreon",
1800
+ "url": "https://www.patreon.com/feross"
1801
+ },
1802
+ {
1803
+ "type": "consulting",
1804
+ "url": "https://feross.org/support"
1805
+ }
1806
+ ],
1807
+ "license": "MIT"
1808
+ },
1809
+ "node_modules/simple-get": {
1810
+ "version": "4.0.1",
1811
+ "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
1812
+ "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
1813
+ "funding": [
1814
+ {
1815
+ "type": "github",
1816
+ "url": "https://github.com/sponsors/feross"
1817
+ },
1818
+ {
1819
+ "type": "patreon",
1820
+ "url": "https://www.patreon.com/feross"
1821
+ },
1822
+ {
1823
+ "type": "consulting",
1824
+ "url": "https://feross.org/support"
1825
+ }
1826
+ ],
1827
+ "license": "MIT",
1828
+ "dependencies": {
1829
+ "decompress-response": "^6.0.0",
1830
+ "once": "^1.3.1",
1831
+ "simple-concat": "^1.0.0"
1832
+ }
1833
+ },
1834
+ "node_modules/simple-swizzle": {
1835
+ "version": "0.2.4",
1836
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
1837
+ "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
1838
+ "license": "MIT",
1839
+ "dependencies": {
1840
+ "is-arrayish": "^0.3.1"
1841
+ }
1842
+ },
1843
+ "node_modules/statuses": {
1844
+ "version": "2.0.2",
1845
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
1846
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
1847
+ "license": "MIT",
1848
+ "engines": {
1849
+ "node": ">= 0.8"
1850
+ }
1851
+ },
1852
+ "node_modules/streamsearch": {
1853
+ "version": "1.1.0",
1854
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
1855
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
1856
+ "engines": {
1857
+ "node": ">=10.0.0"
1858
+ }
1859
+ },
1860
+ "node_modules/streamx": {
1861
+ "version": "2.26.0",
1862
+ "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.26.0.tgz",
1863
+ "integrity": "sha512-VvNG1K72Po/xwJzxZFnZ++Tbrv4lwSptsbkFuzXCJAYZvCK5nnxsvXU6ajqkv7chyiI1Y0YXq2Jh8Iy8Y7NF/A==",
1864
+ "license": "MIT",
1865
+ "dependencies": {
1866
+ "events-universal": "^1.0.0",
1867
+ "fast-fifo": "^1.3.2",
1868
+ "text-decoder": "^1.1.0"
1869
+ }
1870
+ },
1871
+ "node_modules/string_decoder": {
1872
+ "version": "1.3.0",
1873
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
1874
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
1875
+ "license": "MIT",
1876
+ "dependencies": {
1877
+ "safe-buffer": "~5.2.0"
1878
+ }
1879
+ },
1880
+ "node_modules/strip-json-comments": {
1881
+ "version": "2.0.1",
1882
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
1883
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
1884
+ "license": "MIT",
1885
+ "engines": {
1886
+ "node": ">=0.10.0"
1887
+ }
1888
+ },
1889
+ "node_modules/tar-fs": {
1890
+ "version": "3.1.2",
1891
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz",
1892
+ "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==",
1893
+ "license": "MIT",
1894
+ "dependencies": {
1895
+ "pump": "^3.0.0",
1896
+ "tar-stream": "^3.1.5"
1897
+ },
1898
+ "optionalDependencies": {
1899
+ "bare-fs": "^4.0.1",
1900
+ "bare-path": "^3.0.0"
1901
+ }
1902
+ },
1903
+ "node_modules/tar-stream": {
1904
+ "version": "3.2.0",
1905
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz",
1906
+ "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==",
1907
+ "license": "MIT",
1908
+ "dependencies": {
1909
+ "b4a": "^1.6.4",
1910
+ "bare-fs": "^4.5.5",
1911
+ "fast-fifo": "^1.2.0",
1912
+ "streamx": "^2.15.0"
1913
+ }
1914
+ },
1915
+ "node_modules/teex": {
1916
+ "version": "1.0.1",
1917
+ "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
1918
+ "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
1919
+ "license": "MIT",
1920
+ "dependencies": {
1921
+ "streamx": "^2.12.5"
1922
+ }
1923
+ },
1924
+ "node_modules/text-decoder": {
1925
+ "version": "1.2.7",
1926
+ "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
1927
+ "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
1928
+ "license": "Apache-2.0",
1929
+ "dependencies": {
1930
+ "b4a": "^1.6.4"
1931
+ }
1932
+ },
1933
+ "node_modules/toidentifier": {
1934
+ "version": "1.0.1",
1935
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1936
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1937
+ "license": "MIT",
1938
+ "engines": {
1939
+ "node": ">=0.6"
1940
+ }
1941
+ },
1942
+ "node_modules/tunnel-agent": {
1943
+ "version": "0.6.0",
1944
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
1945
+ "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
1946
+ "license": "Apache-2.0",
1947
+ "dependencies": {
1948
+ "safe-buffer": "^5.0.1"
1949
+ },
1950
+ "engines": {
1951
+ "node": "*"
1952
+ }
1953
+ },
1954
+ "node_modules/type-is": {
1955
+ "version": "2.1.0",
1956
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
1957
+ "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
1958
+ "license": "MIT",
1959
+ "dependencies": {
1960
+ "content-type": "^2.0.0",
1961
+ "media-typer": "^1.1.0",
1962
+ "mime-types": "^3.0.0"
1963
+ },
1964
+ "engines": {
1965
+ "node": ">= 18"
1966
+ },
1967
+ "funding": {
1968
+ "type": "opencollective",
1969
+ "url": "https://opencollective.com/express"
1970
+ }
1971
+ },
1972
+ "node_modules/type-is/node_modules/content-type": {
1973
+ "version": "2.0.0",
1974
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
1975
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
1976
+ "license": "MIT",
1977
+ "engines": {
1978
+ "node": ">=18"
1979
+ },
1980
+ "funding": {
1981
+ "type": "opencollective",
1982
+ "url": "https://opencollective.com/express"
1983
+ }
1984
+ },
1985
+ "node_modules/typedarray": {
1986
+ "version": "0.0.6",
1987
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
1988
+ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
1989
+ "license": "MIT"
1990
+ },
1991
+ "node_modules/undici-types": {
1992
+ "version": "7.24.6",
1993
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
1994
+ "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
1995
+ "license": "MIT"
1996
+ },
1997
+ "node_modules/unpipe": {
1998
+ "version": "1.0.0",
1999
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
2000
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
2001
+ "license": "MIT",
2002
+ "engines": {
2003
+ "node": ">= 0.8"
2004
+ }
2005
+ },
2006
+ "node_modules/util-deprecate": {
2007
+ "version": "1.0.2",
2008
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
2009
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
2010
+ "license": "MIT"
2011
+ },
2012
+ "node_modules/vary": {
2013
+ "version": "1.1.2",
2014
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
2015
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
2016
+ "license": "MIT",
2017
+ "engines": {
2018
+ "node": ">= 0.8"
2019
+ }
2020
+ },
2021
+ "node_modules/which": {
2022
+ "version": "1.3.1",
2023
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
2024
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
2025
+ "license": "ISC",
2026
+ "dependencies": {
2027
+ "isexe": "^2.0.0"
2028
+ },
2029
+ "bin": {
2030
+ "which": "bin/which"
2031
+ }
2032
+ },
2033
+ "node_modules/wrappy": {
2034
+ "version": "1.0.2",
2035
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
2036
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
2037
+ "license": "ISC"
2038
+ },
2039
+ "node_modules/yt-dlp-wrap": {
2040
+ "version": "2.3.12",
2041
+ "resolved": "https://registry.npmjs.org/yt-dlp-wrap/-/yt-dlp-wrap-2.3.12.tgz",
2042
+ "integrity": "sha512-P8fJ+6M1YjukyJENCTviNLiZ8mokxprR54ho3DsSKPWDcac489OjRiStGEARJr6un6ETS6goTn4CWl/b/rM3aA==",
2043
+ "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
2044
+ "license": "MIT"
2045
+ }
2046
+ }
2047
+ }
package.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "reel-transcriber",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "start": "node server.js"
8
+ },
9
+ "engines": {
10
+ "node": ">=18"
11
+ },
12
+ "keywords": [],
13
+ "author": "",
14
+ "license": "ISC",
15
+ "type": "commonjs",
16
+ "dependencies": {
17
+ "@xenova/transformers": "^2.17.2",
18
+ "compression": "^1.8.1",
19
+ "express": "^5.2.1",
20
+ "ffmpeg-static": "^5.3.0",
21
+ "fluent-ffmpeg": "^2.1.3",
22
+ "helmet": "^8.2.0",
23
+ "multer": "^2.1.1",
24
+ "yt-dlp-wrap": "^2.3.12"
25
+ }
26
+ }
server.js ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const { execFile, spawn } = require('child_process');
5
+ const { randomUUID } = require('crypto');
6
+ const ffmpegStatic = require('ffmpeg-static');
7
+ const YTDlpWrap = require('yt-dlp-wrap').default;
8
+ const multer = require('multer');
9
+ const helmet = require('helmet');
10
+ const compression = require('compression');
11
+ const { analyze, generateTools } = require('./analyze');
12
+
13
+ // ── Config ────────────────────────────────────────────────────
14
+ const PORT = process.env.PORT || 5050;
15
+ const IS_WIN = process.platform === 'win32';
16
+ const DOWNLOADS_DIR = path.join(__dirname, 'downloads');
17
+ const UPLOADS_DIR = path.join(__dirname, 'uploads');
18
+ const YTDLP_PATH = process.env.YTDLP_PATH ||
19
+ path.join(__dirname, 'bin', IS_WIN ? 'yt-dlp.exe' : 'yt-dlp');
20
+ const JOB_TTL_MS = 60 * 60 * 1000; // 1h: jobs e arquivos temporários expiram
21
+
22
+ [DOWNLOADS_DIR, UPLOADS_DIR, path.join(__dirname, 'bin')].forEach(d => fs.mkdirSync(d, { recursive: true }));
23
+
24
+ // ── App ───────────────────────────────────────────────────────
25
+ const app = express();
26
+ app.set('trust proxy', 1);
27
+ app.use(helmet({
28
+ contentSecurityPolicy: false, // página usa estilos/scripts inline + fontes/thumbs externas
29
+ crossOriginEmbedderPolicy: false,
30
+ crossOriginResourcePolicy: false,
31
+ }));
32
+ app.use(compression());
33
+ app.use(express.json({ limit: '10mb' }));
34
+ app.use(express.static(path.join(__dirname, 'public')));
35
+
36
+ const upload = multer({
37
+ dest: UPLOADS_DIR,
38
+ limits: { fileSize: 2 * 1024 * 1024 * 1024 }, // 2 GB
39
+ });
40
+
41
+ // ── In-memory job store (single instance) ─────────────────────
42
+ const jobs = {};
43
+
44
+ function newJob() {
45
+ const id = randomUUID();
46
+ jobs[id] = { status: 'queued', text: '', error: '', language: '', segments: [], createdAt: Date.now() };
47
+ return id;
48
+ }
49
+ function finishJob(id, result) {
50
+ if (!jobs[id]) return;
51
+ Object.assign(jobs[id], {
52
+ status: 'done',
53
+ text: result.text,
54
+ language: result.language || '',
55
+ segments: result.segments || [],
56
+ });
57
+ }
58
+ function failJob(id, msg) {
59
+ if (jobs[id]) Object.assign(jobs[id], { status: 'error', error: msg });
60
+ console.error(`[job ${id}] erro:`, msg);
61
+ }
62
+
63
+ // Limpeza periódica de jobs antigos + arquivos órfãos
64
+ setInterval(() => {
65
+ const now = Date.now();
66
+ for (const [id, job] of Object.entries(jobs)) {
67
+ if (now - (job.createdAt || 0) > JOB_TTL_MS) delete jobs[id];
68
+ }
69
+ for (const dir of [DOWNLOADS_DIR, UPLOADS_DIR]) {
70
+ fs.readdir(dir, (err, files) => {
71
+ if (err) return;
72
+ files.forEach(f => {
73
+ const fp = path.join(dir, f);
74
+ fs.stat(fp, (e, st) => {
75
+ if (!e && now - st.mtimeMs > JOB_TTL_MS) fs.unlink(fp, () => {});
76
+ });
77
+ });
78
+ });
79
+ }
80
+ }, 15 * 60 * 1000).unref();
81
+
82
+ // ── yt-dlp / ffmpeg helpers ───────────────────────────────────
83
+ let ytdlpReady = null;
84
+ async function ensureYtDlp() {
85
+ if (fs.existsSync(YTDLP_PATH)) return;
86
+ if (!ytdlpReady) {
87
+ ytdlpReady = (async () => {
88
+ console.log('Baixando yt-dlp...');
89
+ await YTDlpWrap.downloadFromGithub(YTDLP_PATH);
90
+ if (!IS_WIN) { try { fs.chmodSync(YTDLP_PATH, 0o755); } catch {} }
91
+ console.log('yt-dlp pronto.');
92
+ })();
93
+ }
94
+ return ytdlpReady;
95
+ }
96
+
97
+ async function getVideoInfo(url) {
98
+ const ytdlp = new YTDlpWrap(YTDLP_PATH);
99
+ const raw = await ytdlp.execPromise([url, '--dump-json', '--no-playlist', '--quiet']);
100
+ const info = JSON.parse(raw);
101
+ return {
102
+ title: info.title || '',
103
+ uploader: info.uploader || info.channel || '',
104
+ thumbnail: info.thumbnail || '',
105
+ duration: info.duration || 0,
106
+ platform: info.extractor_key || '',
107
+ };
108
+ }
109
+
110
+ /** Converte qualquer áudio/vídeo para mp3 mono 16kHz (p/ Whisper) */
111
+ function convertToMp3(inputFile, outMp3, deleteInput = false, hq = false) {
112
+ return new Promise((resolve, reject) => {
113
+ const args = hq
114
+ ? ['-i', inputFile, '-vn', '-ar', '44100', '-ac', '2', '-b:a', '192k', '-y', outMp3]
115
+ : ['-i', inputFile, '-vn', '-ar', '16000', '-ac', '1', '-b:a', '64k', '-y', outMp3];
116
+ execFile(ffmpegStatic, args, { stdio: 'pipe' }, (err) => {
117
+ if (deleteInput) fs.unlink(inputFile, () => {});
118
+ if (err) reject(new Error('Erro ao converter áudio: ' + err.message));
119
+ else resolve();
120
+ });
121
+ });
122
+ }
123
+
124
+ async function downloadAudioFromUrl(jobId, url, setStatus = () => {}) {
125
+ const outMp3 = path.join(DOWNLOADS_DIR, `${jobId}.mp3`);
126
+ const tmpOut = path.join(DOWNLOADS_DIR, `${jobId}.%(ext)s`);
127
+ const ytdlp = new YTDlpWrap(YTDLP_PATH);
128
+ await ytdlp.execPromise([url, '--format', 'bestaudio/best', '--output', tmpOut, '--no-playlist', '--quiet']);
129
+ const files = fs.readdirSync(DOWNLOADS_DIR).filter(f => f.startsWith(jobId) && !f.endsWith('.mp3'));
130
+ if (!files.length) throw new Error('Não foi possível baixar o vídeo. Verifique o link.');
131
+ setStatus('extracting');
132
+ await convertToMp3(path.join(DOWNLOADS_DIR, files[0]), outMp3, true);
133
+ return outMp3;
134
+ }
135
+
136
+ /** Roda o Whisper num processo filho */
137
+ function transcribeAudio(mp3Path, modelName, language, task = 'transcribe') {
138
+ return new Promise((resolve, reject) => {
139
+ const worker = path.join(__dirname, 'whisper_worker.js');
140
+ const child = spawn(process.execPath, [worker, mp3Path, modelName, language, task], { stdio: ['ignore', 'pipe', 'pipe'] });
141
+ let stdout = '', stderr = '', settled = false;
142
+ child.stdout.on('data', d => { stdout += d.toString(); });
143
+ child.stderr.on('data', d => { const l = d.toString().trim(); if (l) { stderr += l + '\n'; console.log('[worker]', l); } });
144
+ child.on('close', code => {
145
+ if (settled) return; settled = true;
146
+ if (code !== 0) return reject(new Error(stderr.split('\n').filter(Boolean).pop() || `Worker saiu com código ${code}`));
147
+ try { resolve(JSON.parse(stdout.trim())); }
148
+ catch { reject(new Error('Resposta inválida do transcritor.')); }
149
+ });
150
+ child.on('error', err => { if (!settled) { settled = true; reject(err); } });
151
+ });
152
+ }
153
+
154
+ // ── Pipelines ─────────────────────────────────────────────────
155
+ async function runUrlJob(jobId, url, model, language, task) {
156
+ let mp3 = null;
157
+ try {
158
+ jobs[jobId].status = 'downloading_ytdlp'; await ensureYtDlp();
159
+ jobs[jobId].status = 'downloading';
160
+ mp3 = await downloadAudioFromUrl(jobId, url, s => { jobs[jobId].status = s; });
161
+ jobs[jobId].status = 'transcribing';
162
+ finishJob(jobId, await transcribeAudio(mp3, model, language, task));
163
+ } catch (e) { failJob(jobId, e.message); }
164
+ finally { if (mp3 && fs.existsSync(mp3)) fs.unlink(mp3, () => {}); }
165
+ }
166
+
167
+ async function runFileJob(jobId, filePath, model, language, task) {
168
+ let mp3 = null;
169
+ try {
170
+ jobs[jobId].status = 'extracting';
171
+ mp3 = path.join(DOWNLOADS_DIR, `${jobId}.mp3`);
172
+ await convertToMp3(filePath, mp3, true);
173
+ jobs[jobId].status = 'transcribing';
174
+ finishJob(jobId, await transcribeAudio(mp3, model, language, task));
175
+ } catch (e) {
176
+ failJob(jobId, e.message);
177
+ if (filePath && fs.existsSync(filePath)) fs.unlink(filePath, () => {});
178
+ } finally { if (mp3 && fs.existsSync(mp3)) fs.unlink(mp3, () => {}); }
179
+ }
180
+
181
+ // ── Routes ────────────────────────────────────────────────────
182
+ app.get('/', (req, res) => res.sendFile(path.join(__dirname, 'templates', 'index.html')));
183
+ app.get('/health', (req, res) => res.json({ ok: true, uptime: process.uptime() }));
184
+
185
+ app.post('/info', async (req, res) => {
186
+ const { url } = req.body;
187
+ if (!url) return res.status(400).json({ error: 'URL não informada' });
188
+ try { await ensureYtDlp(); res.json(await getVideoInfo(url)); }
189
+ catch (e) { res.status(400).json({ error: 'Não foi possível obter informações do vídeo.' }); }
190
+ });
191
+
192
+ app.post('/transcribe', (req, res) => {
193
+ const { url, model = 'Xenova/whisper-small', language = 'auto', task = 'transcribe' } = req.body;
194
+ if (!url) return res.status(400).json({ error: 'URL não informada' });
195
+ const jobId = newJob();
196
+ runUrlJob(jobId, url, model, language, task);
197
+ res.json({ job_id: jobId });
198
+ });
199
+
200
+ app.post('/transcribe-file', upload.single('file'), (req, res) => {
201
+ if (!req.file) return res.status(400).json({ error: 'Nenhum arquivo enviado.' });
202
+ const { model = 'Xenova/whisper-small', language = 'auto', task = 'transcribe' } = req.body;
203
+ const jobId = newJob();
204
+ runFileJob(jobId, req.file.path, model, language, task);
205
+ res.json({ job_id: jobId });
206
+ });
207
+
208
+ app.post('/analyze', (req, res) => {
209
+ const { text, segments, title } = req.body;
210
+ if (!text) return res.status(400).json({ error: 'Texto não informado' });
211
+ try { res.json(analyze(text, segments || [], title || '')); }
212
+ catch (e) { res.status(400).json({ error: e.message }); }
213
+ });
214
+
215
+ app.post('/generate', (req, res) => {
216
+ const { type, ...params } = req.body;
217
+ if (!type) return res.status(400).json({ error: 'Tipo de ferramenta não informado' });
218
+ try { res.json(generateTools(type, params)); }
219
+ catch (e) { res.status(400).json({ error: e.message }); }
220
+ });
221
+
222
+ app.get('/status/:jobId', (req, res) => {
223
+ const job = jobs[req.params.jobId];
224
+ if (!job) return res.status(404).json({ error: 'Job não encontrado' });
225
+ res.json(job);
226
+ });
227
+
228
+ /** Stream helper: envia arquivo e remove ao terminar */
229
+ function streamAndCleanup(res, filePath, downloadName, contentType) {
230
+ res.setHeader('Content-Disposition', `attachment; filename="${downloadName}"`);
231
+ res.setHeader('Content-Type', contentType);
232
+ const stream = fs.createReadStream(filePath);
233
+ stream.pipe(res);
234
+ const cleanup = () => fs.unlink(filePath, () => {});
235
+ stream.on('close', cleanup);
236
+ res.on('close', cleanup);
237
+ }
238
+
239
+ app.post('/download-video', async (req, res) => {
240
+ const { url } = req.body;
241
+ if (!url) return res.status(400).json({ error: 'URL não informada' });
242
+ try {
243
+ await ensureYtDlp();
244
+ const id = randomUUID();
245
+ const out = path.join(DOWNLOADS_DIR, `${id}.%(ext)s`);
246
+ const ytdlp = new YTDlpWrap(YTDLP_PATH);
247
+ await ytdlp.execPromise([url,
248
+ '--format', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
249
+ '--merge-output-format', 'mp4',
250
+ '--ffmpeg-location', ffmpegStatic,
251
+ '--output', out, '--no-playlist', '--quiet']);
252
+ const files = fs.readdirSync(DOWNLOADS_DIR).filter(f => f.startsWith(id));
253
+ if (!files.length) return res.status(500).json({ error: 'Falha ao baixar vídeo.' });
254
+ streamAndCleanup(res, path.join(DOWNLOADS_DIR, files[0]), 'video.mp4', 'video/mp4');
255
+ } catch (e) { res.status(500).json({ error: 'Falha ao baixar vídeo. Pode estar protegido ou indisponível.' }); }
256
+ });
257
+
258
+ app.post('/download-audio', async (req, res) => {
259
+ const { url } = req.body;
260
+ if (!url) return res.status(400).json({ error: 'URL não informada' });
261
+ try {
262
+ await ensureYtDlp();
263
+ const id = randomUUID();
264
+ const mp3Out = path.join(DOWNLOADS_DIR, `${id}.mp3`);
265
+ const tmpOut = path.join(DOWNLOADS_DIR, `${id}.%(ext)s`);
266
+ const ytdlp = new YTDlpWrap(YTDLP_PATH);
267
+ await ytdlp.execPromise([url, '--format', 'bestaudio/best', '--output', tmpOut, '--no-playlist', '--quiet']);
268
+ const files = fs.readdirSync(DOWNLOADS_DIR).filter(f => f.startsWith(id) && !f.endsWith('.mp3'));
269
+ if (!files.length) return res.status(500).json({ error: 'Falha ao baixar áudio.' });
270
+ await convertToMp3(path.join(DOWNLOADS_DIR, files[0]), mp3Out, true, true);
271
+ streamAndCleanup(res, mp3Out, 'audio.mp3', 'audio/mpeg');
272
+ } catch (e) { res.status(500).json({ error: 'Falha ao baixar áudio.' }); }
273
+ });
274
+
275
+ // Handler global de erros (ex.: arquivo grande demais no multer)
276
+ app.use((err, req, res, next) => {
277
+ console.error('Erro não tratado:', err.message);
278
+ if (res.headersSent) return next(err);
279
+ res.status(500).json({ error: 'Erro interno. Tente novamente.' });
280
+ });
281
+
282
+ // ── Start + graceful shutdown ─────────────────────────────────
283
+ const server = app.listen(PORT, () => {
284
+ console.log(`\n✅ CreatorKit rodando em http://localhost:${PORT} (${process.env.NODE_ENV || 'development'})\n`);
285
+ });
286
+
287
+ function shutdown(sig) {
288
+ console.log(`\n${sig} recebido, encerrando...`);
289
+ server.close(() => process.exit(0));
290
+ setTimeout(() => process.exit(0), 10000).unref();
291
+ }
292
+ ['SIGTERM', 'SIGINT'].forEach(s => process.on(s, () => shutdown(s)));
293
+ process.on('unhandledRejection', r => console.error('Rejeição não tratada:', r));
start.bat ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @echo off
2
+ echo Iniciando Transcritor de Reels...
3
+ cd /d "%~dp0"
4
+
5
+ where node >nul 2>&1
6
+ if %errorlevel% neq 0 (
7
+ echo ERRO: Node.js nao encontrado. Instale em https://nodejs.org
8
+ pause
9
+ exit /b 1
10
+ )
11
+
12
+ if not exist node_modules (
13
+ echo Instalando dependencias pela primeira vez...
14
+ npm install
15
+ )
16
+
17
+ echo.
18
+ echo Acesse: http://localhost:5050
19
+ echo Pressione Ctrl+C para encerrar.
20
+ echo.
21
+ node server.js
22
+ pause
templates/index.html ADDED
@@ -0,0 +1,955 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="pt-BR">
3
+ <head>
4
+ <meta charset="UTF-8"/>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1.0"/>
6
+ <title>CreatorKit — O kit completo do criador de conteúdo</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com"/>
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
9
+ <link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600;9..144,700&family=Hanken+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"/>
10
+ <style>
11
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
12
+ :root{
13
+ --bg:#f7f5f0;--bg2:#fffefb;--ink:#1a1814;--ink2:#5c574e;--ink3:#8f8a7e;
14
+ --line:#e7e3d9;--line2:#d8d3c6;--card:#fffefb;
15
+ --acc:#5b3df5;--acc-d:#4a2fd0;--acc-soft:#ece8ff;
16
+ --pink:#e84e8a;--amber:#f59e0b;--teal:#0d9488;--rose:#f43f5e;
17
+ --ok:#16a34a;--err:#dc2626;
18
+ --r:16px;--rs:10px;--rx:8px;
19
+ --sh:0 1px 2px rgba(26,24,20,.04),0 4px 16px rgba(26,24,20,.05);
20
+ --sh2:0 2px 4px rgba(26,24,20,.05),0 12px 40px rgba(26,24,20,.09);
21
+ }
22
+ html{scroll-behavior:smooth}
23
+ body{background:var(--bg);color:var(--ink);font-family:'Hanken Grotesk',system-ui,sans-serif;font-size:15px;line-height:1.5;-webkit-font-smoothing:antialiased}
24
+ ::selection{background:var(--acc-soft);color:var(--acc-d)}
25
+ ::-webkit-scrollbar{width:8px;height:8px}
26
+ ::-webkit-scrollbar-track{background:transparent}
27
+ ::-webkit-scrollbar-thumb{background:var(--line2);border-radius:4px}
28
+ .serif{font-family:'Fraunces',serif}
29
+ a{color:inherit;text-decoration:none}
30
+ .view{display:none}.view.on{display:block}
31
+
32
+ /* ════ NAV ════ */
33
+ .nav{position:sticky;top:0;z-index:90;display:flex;align-items:center;justify-content:space-between;padding:0 28px;height:64px;background:rgba(247,245,240,.82);backdrop-filter:blur(14px);border-bottom:1px solid var(--line)}
34
+ .brand{display:flex;align-items:center;gap:10px;cursor:pointer}
35
+ .bmark{width:30px;height:30px;border-radius:9px;background:var(--ink);color:var(--bg2);display:flex;align-items:center;justify-content:center;font-family:'Fraunces',serif;font-weight:700;font-size:15px}
36
+ .bname{font-family:'Fraunces',serif;font-weight:600;font-size:19px;letter-spacing:-.01em}
37
+ .nlinks{display:flex;align-items:center;gap:30px}
38
+ .nlink{font-size:14px;color:var(--ink2);font-weight:500;transition:color .15s}
39
+ .nlink:hover{color:var(--ink)}
40
+ .nbtn{background:var(--ink);color:var(--bg2);padding:9px 18px;border-radius:100px;font-size:14px;font-weight:600;border:none;cursor:pointer;transition:transform .12s,background .15s}
41
+ .nbtn:hover{transform:translateY(-1px);background:#000}
42
+ @media(max-width:760px){.nlinks .nlink{display:none}}
43
+
44
+ /* ════ HERO ════ */
45
+ .hero{max-width:1080px;margin:0 auto;padding:84px 28px 64px;text-align:center}
46
+ .hpill{display:inline-flex;align-items:center;gap:7px;background:var(--bg2);border:1px solid var(--line);border-radius:100px;padding:6px 14px;font-size:13px;color:var(--ink2);font-weight:500;margin-bottom:28px;box-shadow:var(--sh)}
47
+ .hpill .dot{width:7px;height:7px;border-radius:50%;background:var(--ok);box-shadow:0 0 0 3px rgba(22,163,74,.15)}
48
+ .htitle{font-family:'Fraunces',serif;font-weight:600;font-size:clamp(2.6rem,6.5vw,4.6rem);line-height:1.02;letter-spacing:-.03em;margin-bottom:22px}
49
+ .htitle em{font-style:italic;color:var(--acc)}
50
+ .hsub{font-size:clamp(1rem,2.2vw,1.22rem);color:var(--ink2);max-width:600px;margin:0 auto 36px;line-height:1.55}
51
+ .hcta{display:flex;gap:12px;justify-content:center;flex-wrap:wrap}
52
+ .btn-lg{padding:14px 28px;border-radius:100px;font-size:15px;font-weight:600;cursor:pointer;border:none;transition:transform .12s,box-shadow .15s,background .15s;font-family:inherit}
53
+ .btn-primary{background:var(--acc);color:#fff;box-shadow:0 6px 22px rgba(91,61,245,.32)}
54
+ .btn-primary:hover{transform:translateY(-2px);background:var(--acc-d);box-shadow:0 10px 30px rgba(91,61,245,.4)}
55
+ .btn-ghost{background:var(--bg2);color:var(--ink);border:1px solid var(--line2)}
56
+ .btn-ghost:hover{transform:translateY(-2px);box-shadow:var(--sh)}
57
+ .hnote{margin-top:18px;font-size:13px;color:var(--ink3)}
58
+
59
+ /* hero mock */
60
+ .hmock{max-width:920px;margin:56px auto 0;background:var(--card);border:1px solid var(--line);border-radius:20px;box-shadow:var(--sh2);overflow:hidden}
61
+ .hmock-bar{display:flex;align-items:center;gap:7px;padding:13px 16px;border-bottom:1px solid var(--line);background:var(--bg)}
62
+ .hmock-bar .c{width:11px;height:11px;border-radius:50%}
63
+ .hmock-bar .c1{background:#ff5f57}.hmock-bar .c2{background:#febc2e}.hmock-bar .c3{background:#28c840}
64
+ .hmock-bar .u{margin-left:12px;font-size:12px;color:var(--ink3);font-family:'JetBrains Mono',monospace}
65
+ .hmock-body{display:grid;grid-template-columns:1.1fr 1fr;gap:0}
66
+ @media(max-width:680px){.hmock-body{grid-template-columns:1fr}}
67
+ .hmock-l{padding:24px;border-right:1px solid var(--line);text-align:left}
68
+ .hmock-r{padding:24px;text-align:left;background:var(--bg)}
69
+ .hmock-label{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--ink3);margin-bottom:10px}
70
+ .hmock-input{background:var(--bg2);border:1px solid var(--line2);border-radius:9px;padding:11px 13px;font-size:13px;color:var(--ink2);margin-bottom:14px;font-family:'JetBrains Mono',monospace}
71
+ .hmock-tags{display:flex;flex-wrap:wrap;gap:6px}
72
+ .hmock-tag{background:var(--acc-soft);color:var(--acc-d);border-radius:100px;padding:4px 11px;font-size:12px;font-weight:600}
73
+ .hmock-line{height:9px;border-radius:5px;background:var(--line);margin-bottom:9px}
74
+ .hmock-line.s{width:70%}.hmock-line.m{width:90%}
75
+
76
+ /* ════ MARQUEE / TRUST ════ */
77
+ .trust{padding:40px 28px;text-align:center;border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
78
+ .trust-l{font-size:13px;color:var(--ink3);font-weight:600;text-transform:uppercase;letter-spacing:.1em;margin-bottom:22px}
79
+ .trust-row{display:flex;justify-content:center;gap:40px;flex-wrap:wrap;font-family:'Fraunces',serif;font-size:20px;color:var(--ink2);font-weight:500}
80
+ .trust-row span{display:flex;align-items:center;gap:8px}
81
+
82
+ /* ════ SECTION ════ */
83
+ .sec{max-width:1120px;margin:0 auto;padding:88px 28px}
84
+ .sec-head{text-align:center;max-width:640px;margin:0 auto 56px}
85
+ .sec-tag{display:inline-block;font-size:13px;font-weight:700;color:var(--acc);text-transform:uppercase;letter-spacing:.1em;margin-bottom:14px}
86
+ .sec-title{font-family:'Fraunces',serif;font-weight:600;font-size:clamp(2rem,4.5vw,3rem);line-height:1.08;letter-spacing:-.02em;margin-bottom:16px}
87
+ .sec-sub{font-size:1.08rem;color:var(--ink2);line-height:1.55}
88
+
89
+ /* feature grid */
90
+ .fgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:18px}
91
+ @media(max-width:880px){.fgrid{grid-template-columns:1fr 1fr}}
92
+ @media(max-width:560px){.fgrid{grid-template-columns:1fr}}
93
+ .fcard{background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:26px;transition:transform .18s,box-shadow .18s,border-color .18s;cursor:pointer;position:relative;overflow:hidden}
94
+ .fcard:hover{transform:translateY(-4px);box-shadow:var(--sh2);border-color:var(--line2)}
95
+ .ficon{width:46px;height:46px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:22px;margin-bottom:16px}
96
+ .fname{font-family:'Fraunces',serif;font-weight:600;font-size:1.25rem;margin-bottom:7px;letter-spacing:-.01em}
97
+ .fdesc{font-size:14px;color:var(--ink2);line-height:1.5}
98
+ .farrow{position:absolute;top:26px;right:26px;color:var(--ink3);opacity:0;transform:translateX(-6px);transition:all .2s;font-size:18px}
99
+ .fcard:hover .farrow{opacity:1;transform:translateX(0)}
100
+
101
+ /* how it works */
102
+ .steps3{display:grid;grid-template-columns:repeat(3,1fr);gap:24px;counter-reset:s}
103
+ @media(max-width:760px){.steps3{grid-template-columns:1fr}}
104
+ .s3{position:relative;padding-top:20px}
105
+ .s3::before{counter-increment:s;content:"0" counter(s);font-family:'Fraunces',serif;font-size:3rem;font-weight:600;color:var(--acc-soft);line-height:1;display:block;margin-bottom:14px}
106
+ .s3 h4{font-family:'Fraunces',serif;font-size:1.3rem;font-weight:600;margin-bottom:8px}
107
+ .s3 p{font-size:14px;color:var(--ink2);line-height:1.55}
108
+
109
+ /* big cta */
110
+ .cta-band{max-width:1120px;margin:40px auto 88px;padding:0 28px}
111
+ .cta-inner{background:var(--ink);border-radius:28px;padding:64px 40px;text-align:center;position:relative;overflow:hidden}
112
+ .cta-inner::before{content:'';position:absolute;inset:0;background:radial-gradient(ellipse 60% 80% at 50% 0%,rgba(91,61,245,.35),transparent 70%);pointer-events:none}
113
+ .cta-inner h2{font-family:'Fraunces',serif;font-weight:600;font-size:clamp(2rem,4.5vw,3rem);color:var(--bg2);line-height:1.08;letter-spacing:-.02em;margin-bottom:16px;position:relative}
114
+ .cta-inner p{color:rgba(255,255,255,.7);font-size:1.05rem;margin-bottom:30px;position:relative}
115
+
116
+ /* footer */
117
+ .foot{border-top:1px solid var(--line);padding:40px 28px}
118
+ .foot-in{max-width:1120px;margin:0 auto;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:16px}
119
+ .foot-c{font-size:13px;color:var(--ink3)}
120
+
121
+ /* ════════════════════════════════════════════════════════════
122
+ APP VIEW
123
+ ════════════════════════════════════════════════════════════ */
124
+ .app-shell{display:flex;height:100vh;overflow:hidden}
125
+ .app-side{width:248px;flex-shrink:0;background:var(--bg2);border-right:1px solid var(--line);display:flex;flex-direction:column;overflow-y:auto}
126
+ .app-side-top{padding:18px 16px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:10px;cursor:pointer}
127
+ .tool-nav{padding:12px 10px;display:flex;flex-direction:column;gap:2px;flex:1}
128
+ .tn-label{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--ink3);padding:12px 10px 6px}
129
+ .tn{display:flex;align-items:center;gap:11px;padding:9px 11px;border-radius:var(--rx);font-size:14px;font-weight:500;color:var(--ink2);cursor:pointer;transition:background .12s,color .12s}
130
+ .tn:hover{background:var(--bg);color:var(--ink)}
131
+ .tn.on{background:var(--acc-soft);color:var(--acc-d);font-weight:600}
132
+ .tn .tn-ico{font-size:16px;width:20px;text-align:center}
133
+ .app-main{flex:1;overflow-y:auto;background:var(--bg)}
134
+ .app-head{padding:22px 32px 0}
135
+ .app-h-title{font-family:'Fraunces',serif;font-size:1.7rem;font-weight:600;letter-spacing:-.01em}
136
+ .app-h-sub{font-size:14px;color:var(--ink2);margin-top:4px}
137
+ .app-body{padding:24px 32px 60px}
138
+ .tool-pane{display:none}.tool-pane.on{display:block;animation:fade .25s ease}
139
+ @keyframes fade{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
140
+
141
+ /* form bits */
142
+ .fld{margin-bottom:14px}
143
+ .fld-l{display:block;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--ink3);margin-bottom:7px}
144
+ .inp,.sel,.ta{width:100%;background:var(--bg2);border:1px solid var(--line2);border-radius:var(--rs);color:var(--ink);font-family:inherit;font-size:14px;padding:11px 13px;outline:none;transition:border-color .15s,box-shadow .15s}
145
+ .inp:focus,.sel:focus,.ta:focus{border-color:var(--acc);box-shadow:0 0 0 3px var(--acc-soft)}
146
+ .ta{resize:vertical;min-height:90px;line-height:1.5}
147
+ .row2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
148
+ @media(max-width:560px){.row2{grid-template-columns:1fr}}
149
+ .urlw{position:relative}
150
+ .urlw .pl{position:absolute;left:12px;top:50%;transform:translateY(-50%);font-size:15px;pointer-events:none}
151
+ .urlw .inp{padding-left:36px;padding-right:60px}
152
+ .pbtn{position:absolute;right:7px;top:50%;transform:translateY(-50%);background:var(--bg);border:1px solid var(--line2);border-radius:6px;color:var(--ink2);font-size:12px;font-weight:600;padding:4px 9px;cursor:pointer;font-family:inherit;transition:background .12s}
153
+ .pbtn:hover{background:var(--line)}
154
+
155
+ .run-btn{width:100%;background:var(--acc);color:#fff;border:none;border-radius:var(--rs);padding:13px;font-size:15px;font-weight:600;font-family:inherit;cursor:pointer;transition:transform .12s,background .15s,box-shadow .15s;box-shadow:0 4px 16px rgba(91,61,245,.28)}
156
+ .run-btn:hover:not(:disabled){transform:translateY(-1px);background:var(--acc-d);box-shadow:0 8px 24px rgba(91,61,245,.36)}
157
+ .run-btn:disabled{opacity:.45;cursor:not-allowed;transform:none;box-shadow:none}
158
+
159
+ .card-grid{display:grid;grid-template-columns:1fr 1fr;gap:24px}
160
+ @media(max-width:880px){.card-grid{grid-template-columns:1fr}}
161
+ .panel{background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:22px}
162
+ .panel-t{font-family:'Fraunces',serif;font-size:1.15rem;font-weight:600;margin-bottom:16px}
163
+
164
+ /* tabs (input switch) */
165
+ .itabs{display:flex;background:var(--bg);border:1px solid var(--line2);border-radius:var(--rs);padding:3px;gap:3px;margin-bottom:14px}
166
+ .itab{flex:1;padding:8px;border-radius:7px;font-size:13px;font-weight:600;color:var(--ink2);cursor:pointer;text-align:center;transition:all .12s}
167
+ .itab.on{background:var(--card);color:var(--ink);box-shadow:var(--sh)}
168
+ .tpane{display:none}.tpane.on{display:block}
169
+
170
+ /* drop */
171
+ .dz{border:2px dashed var(--line2);border-radius:var(--rs);padding:26px 14px;text-align:center;cursor:pointer;transition:all .15s}
172
+ .dz:hover,.dz.ov{border-color:var(--acc);background:var(--acc-soft)}
173
+ .dz .i{font-size:24px;margin-bottom:6px}.dz .l{font-size:13px;color:var(--ink2);font-weight:500}.dz .s{font-size:12px;color:var(--ink3);margin-top:3px}
174
+ .fchosen{display:none;margin-top:9px;background:var(--bg);border:1px solid var(--line2);border-radius:var(--rs);padding:9px 12px;font-size:13px;align-items:center;gap:8px}
175
+ .fchosen.on{display:flex}
176
+ .fx{margin-left:auto;background:none;border:none;color:var(--ink3);cursor:pointer;font-size:15px}
177
+ .fx:hover{color:var(--err)}
178
+
179
+ /* video preview */
180
+ .vprev{display:none;margin-top:10px;background:var(--bg);border:1px solid var(--line2);border-radius:var(--rs);padding:9px;gap:10px;align-items:center}
181
+ .vprev.on{display:flex}
182
+ .vprev img{width:64px;height:42px;object-fit:cover;border-radius:6px;background:var(--line)}
183
+ .vprev .t{font-size:13px;font-weight:600;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
184
+ .vprev .m{font-size:11px;color:var(--ink3);margin-top:2px}
185
+
186
+ /* steps */
187
+ .pg{display:none;margin-top:16px}
188
+ .steps{display:flex;position:relative}
189
+ .steps::before{content:'';position:absolute;top:11px;left:11px;right:11px;height:2px;background:var(--line2)}
190
+ .step{flex:1;display:flex;flex-direction:column;align-items:center;gap:5px;position:relative;z-index:1}
191
+ .sc{width:23px;height:23px;border-radius:50%;background:var(--bg2);border:2px solid var(--line2);display:flex;align-items:center;justify-content:center;font-size:10px;transition:all .3s}
192
+ .step.ac .sc{border-color:var(--acc);background:var(--acc-soft)}
193
+ .step.ac .sc::after{content:'';width:8px;height:8px;border-radius:50%;background:var(--acc);animation:pulse .8s ease-in-out infinite alternate}
194
+ .step.dn .sc{border-color:var(--ok);background:#dcfce7}
195
+ .step.dn .sc::after{content:'✓';font-size:10px;color:var(--ok);font-weight:700}
196
+ @keyframes pulse{from{transform:scale(.6);opacity:.5}to{transform:scale(1);opacity:1}}
197
+ .slb{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--ink3)}
198
+ .step.ac .slb{color:var(--ink2)}.step.dn .slb{color:var(--ok)}
199
+
200
+ .errbox{display:none;margin-top:14px;background:#fef2f2;border:1px solid #fecaca;border-radius:var(--rs);padding:12px;font-size:13px;color:#b91c1c;line-height:1.5}
201
+
202
+ /* result tabs */
203
+ .outc{display:none}.outc.on{display:block}
204
+ .sbar{display:flex;gap:7px;flex-wrap:wrap;margin-bottom:16px}
205
+ .schip{display:flex;align-items:center;gap:4px;background:var(--bg2);border:1px solid var(--line2);border-radius:100px;padding:4px 11px;font-size:12px;color:var(--ink2);font-weight:500}
206
+ .otnav{display:flex;gap:2px;border-bottom:1px solid var(--line);overflow-x:auto;margin-bottom:18px;padding-bottom:0}
207
+ .otnav::-webkit-scrollbar{height:0}
208
+ .otab{padding:9px 13px;font-size:13px;font-weight:600;color:var(--ink2);cursor:pointer;border-bottom:2px solid transparent;white-space:nowrap;transition:all .12s}
209
+ .otab:hover{color:var(--ink)}
210
+ .otab.on{color:var(--acc);border-bottom-color:var(--acc)}
211
+ .opanel{display:none}.opanel.on{display:block;animation:fade .2s ease}
212
+
213
+ /* content box */
214
+ .cb{background:var(--card);border:1px solid var(--line);border-radius:var(--r);overflow:hidden;margin-bottom:12px}
215
+ .cbh{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;padding:12px 16px;border-bottom:1px solid var(--line);background:var(--bg2)}
216
+ .cbt{font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--ink3)}
217
+ .cba{display:flex;gap:6px;flex-wrap:wrap}
218
+ .cbb{padding:16px;font-size:14px;line-height:1.7;white-space:pre-wrap;word-break:break-word;max-height:360px;overflow-y:auto}
219
+ .cbb.mono{font-family:'JetBrains Mono',monospace;font-size:12.5px;color:var(--ink2)}
220
+
221
+ .btn{display:inline-flex;align-items:center;gap:5px;background:var(--bg);border:1px solid var(--line2);border-radius:7px;color:var(--ink2);font-family:inherit;font-size:12px;font-weight:600;padding:6px 11px;cursor:pointer;transition:all .12s;white-space:nowrap}
222
+ .btn:hover{background:var(--line);color:var(--ink)}
223
+ .btn.pri{background:var(--acc-soft);border-color:transparent;color:var(--acc-d)}
224
+ .btn.pri:hover{background:#ddd6ff}
225
+ .btn.ok{color:var(--ok)!important;border-color:#bbf7d0!important;background:#f0fdf4!important}
226
+ .btn.ld{opacity:.5;pointer-events:none}
227
+ .btn.sm{padding:4px 9px;font-size:11px}
228
+
229
+ /* search */
230
+ .srchw{position:relative;margin-bottom:12px}
231
+ .srchi{width:100%;background:var(--bg2);border:1px solid var(--line2);border-radius:var(--rs);font-family:inherit;font-size:13px;padding:9px 60px 9px 32px;outline:none;transition:border-color .15s}
232
+ .srchi:focus{border-color:var(--acc)}
233
+ .sico{position:absolute;left:11px;top:50%;transform:translateY(-50%);color:var(--ink3);font-size:13px}
234
+ .scnt{position:absolute;right:11px;top:50%;transform:translateY(-50%);font-size:11px;color:var(--ink3)}
235
+ mark{background:#fde68a;border-radius:2px;padding:0 1px}
236
+
237
+ /* segments */
238
+ .segtog{display:flex;align-items:center;justify-content:space-between;padding:11px 16px;border-top:1px solid var(--line);cursor:pointer;background:var(--bg2)}
239
+ .segtog:hover{background:var(--bg)}
240
+ .segarr{font-size:11px;color:var(--ink3);transition:transform .2s}.segarr.op{transform:rotate(180deg)}
241
+ .seglist{display:none}
242
+ .segitem{display:flex;gap:10px;padding:9px 16px;border-bottom:1px solid var(--line);font-size:13px}
243
+ .segitem:last-child{border-bottom:none}
244
+ .segtime{color:var(--acc);font-family:'JetBrains Mono',monospace;font-size:11px;width:78px;flex-shrink:0;padding-top:2px}
245
+ .segtxt{color:var(--ink2);flex:1;line-height:1.5}
246
+ .segcp{opacity:0;background:none;border:none;color:var(--ink3);cursor:pointer;font-size:11px;transition:opacity .12s}
247
+ .segitem:hover .segcp{opacity:1}
248
+
249
+ /* clouds */
250
+ .cloud{display:flex;flex-wrap:wrap;gap:7px;padding:16px}
251
+ .tag{border-radius:100px;padding:5px 12px;font-size:13px;font-weight:500;cursor:pointer;transition:all .12s}
252
+ .tag.kw{background:var(--bg);border:1px solid var(--line2);color:var(--ink2)}
253
+ .tag.kw.top{background:var(--acc-soft);border-color:transparent;color:var(--acc-d);font-weight:600}
254
+ .tag.ht{background:#eef6ff;border:1px solid #cfe4ff;color:#1d6fd4}
255
+ .tag.ht:hover{background:#dcecff}
256
+ .tag.ht.cp{background:#f0fdf4;border-color:#bbf7d0;color:var(--ok)}
257
+
258
+ /* list items (hooks, ideas, titles) */
259
+ .litem{display:flex;align-items:center;gap:12px;padding:13px 16px;border-bottom:1px solid var(--line);transition:background .1s}
260
+ .litem:last-child{border-bottom:none}
261
+ .litem:hover{background:var(--bg)}
262
+ .litem .num{font-family:'Fraunces',serif;font-size:1.1rem;font-weight:600;color:var(--acc-soft);width:26px;flex-shrink:0}
263
+ .litem .txt{flex:1;font-size:14px;line-height:1.45}
264
+ .fmt-badge{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;padding:3px 8px;border-radius:100px;background:var(--acc-soft);color:var(--acc-d);flex-shrink:0}
265
+
266
+ /* tweet cards */
267
+ .tw{background:var(--bg2);border:1px solid var(--line2);border-radius:var(--r);padding:14px 16px;margin-bottom:8px;font-size:14px;line-height:1.6;white-space:pre-wrap}
268
+
269
+ /* download cards */
270
+ .dlg{display:grid;grid-template-columns:1fr 1fr;gap:10px;padding:16px}
271
+ @media(max-width:560px){.dlg{grid-template-columns:1fr}}
272
+ .dlc{background:var(--bg2);border:1px solid var(--line2);border-radius:var(--r);padding:15px;display:flex;flex-direction:column;gap:6px}
273
+ .dlc .i{font-size:22px}.dlc .n{font-weight:700;font-size:14px}.dlc .d{font-size:12px;color:var(--ink3);line-height:1.4}
274
+
275
+ /* bio cards */
276
+ .biocard{background:var(--bg2);border:1px solid var(--line2);border-radius:var(--r);padding:16px;margin-bottom:10px;white-space:pre-wrap;font-size:14px;line-height:1.6;position:relative}
277
+ .biocard .cp{position:absolute;top:12px;right:12px}
278
+
279
+ /* empty */
280
+ .empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:64px 24px;text-align:center;gap:14px}
281
+ .empty .g{width:88px;height:88px;border-radius:50%;background:radial-gradient(circle,var(--acc-soft),transparent 72%);display:flex;align-items:center;justify-content:center;font-size:38px}
282
+ .empty h3{font-family:'Fraunces',serif;font-size:1.4rem;font-weight:600}
283
+ .empty p{font-size:14px;color:var(--ink2);max-width:340px;line-height:1.55}
284
+
285
+ /* hint banner */
286
+ .hint{background:var(--acc-soft);border:1px solid #d9d0ff;border-radius:var(--rs);padding:11px 14px;font-size:13px;color:var(--acc-d);line-height:1.5;margin-bottom:18px}
287
+ .hint b{font-weight:700}
288
+ </style>
289
+ </head>
290
+ <body>
291
+
292
+ <!-- ═══════════════ LANDING VIEW ═══════════════ -->
293
+ <div class="view on" id="view-landing">
294
+
295
+ <nav class="nav">
296
+ <div class="brand" onclick="goLanding()">
297
+ <div class="bmark">CK</div>
298
+ <span class="bname">CreatorKit</span>
299
+ </div>
300
+ <div class="nlinks">
301
+ <a class="nlink" href="#tools">Ferramentas</a>
302
+ <a class="nlink" href="#how">Como funciona</a>
303
+ <a class="nlink" href="#why">Por quê</a>
304
+ <button class="nbtn" onclick="openApp()">Abrir app →</button>
305
+ </div>
306
+ </nav>
307
+
308
+ <!-- HERO -->
309
+ <section class="hero">
310
+ <div class="hpill"><span class="dot"></span> 7 ferramentas · 100% local · sem mensalidade</div>
311
+ <h1 class="htitle">O kit completo<br>do <em>criador de conteúdo</em></h1>
312
+ <p class="hsub">Transcreva vídeos, gere hooks que prendem, descubra ideias, crie hashtags, bios e um calendário de postagens — tudo em um só lugar, em segundos.</p>
313
+ <div class="hcta">
314
+ <button class="btn-lg btn-primary" onclick="openApp()">Começar agora — é grátis</button>
315
+ <button class="btn-lg btn-ghost" onclick="document.getElementById('tools').scrollIntoView()">Ver ferramentas</button>
316
+ </div>
317
+ <div class="hnote">✦ Roda no seu computador · seus dados nunca saem da sua máquina</div>
318
+
319
+ <!-- HERO MOCK -->
320
+ <div class="hmock">
321
+ <div class="hmock-bar"><span class="c c1"></span><span class="c c2"></span><span class="c c3"></span><span class="u">creatorkit.app/estudio</span></div>
322
+ <div class="hmock-body">
323
+ <div class="hmock-l">
324
+ <div class="hmock-label">🎬 Estúdio de vídeo</div>
325
+ <div class="hmock-input">https://instagram.com/reel/...</div>
326
+ <div class="hmock-label">Gerando conteúdo</div>
327
+ <div class="hmock-line m"></div><div class="hmock-line s"></div><div class="hmock-line m"></div>
328
+ </div>
329
+ <div class="hmock-r">
330
+ <div class="hmock-label"># Hashtags geradas</div>
331
+ <div class="hmock-tags">
332
+ <span class="hmock-tag">#marketing</span><span class="hmock-tag">#conteúdo</span>
333
+ <span class="hmock-tag">#dicas</span><span class="hmock-tag">#viral</span>
334
+ <span class="hmock-tag">#reels</span><span class="hmock-tag">#criador</span>
335
+ <span class="hmock-tag">#engajamento</span><span class="hmock-tag">#instagram</span>
336
+ </div>
337
+ </div>
338
+ </div>
339
+ </div>
340
+ </section>
341
+
342
+ <!-- TRUST -->
343
+ <section class="trust">
344
+ <div class="trust-l">Tudo que você posta, em um fluxo só</div>
345
+ <div class="trust-row">
346
+ <span>📸 Instagram</span><span>▶️ YouTube</span><span>🎵 TikTok</span>
347
+ <span>𝕏 Twitter</span><span>💼 LinkedIn</span><span>📰 Blog</span>
348
+ </div>
349
+ </section>
350
+
351
+ <!-- TOOLS / FEATURES -->
352
+ <section class="sec" id="tools">
353
+ <div class="sec-head">
354
+ <span class="sec-tag">Ferramentas</span>
355
+ <h2 class="sec-title">Seis ferramentas, um só painel</h2>
356
+ <p class="sec-sub">Cada uma resolve uma dor real de quem cria conteúdo todos os dias.</p>
357
+ </div>
358
+ <div class="fgrid" id="fgrid">
359
+ <div class="fcard" onclick="openApp('video')">
360
+ <div class="ficon" style="background:#ece8ff">🎬</div>
361
+ <div class="fname">Estúdio de Vídeo</div>
362
+ <div class="fdesc">Cole um link ou suba um arquivo. Gera transcrição, resumo, caption, thread, post de blog e mais — tudo do áudio do vídeo.</div>
363
+ <span class="farrow">→</span>
364
+ </div>
365
+ <div class="fcard" onclick="openApp('hooks')">
366
+ <div class="ficon" style="background:#fde8f1">🪝</div>
367
+ <div class="fname">Gerador de Hooks</div>
368
+ <div class="fdesc">As primeiras frases decidem se alguém assiste ou rola pra baixo. Gere 10 ganchos prontos a partir do seu tema.</div>
369
+ <span class="farrow">→</span>
370
+ </div>
371
+ <div class="fcard" onclick="openApp('ideas')">
372
+ <div class="ficon" style="background:#fef3c7">💡</div>
373
+ <div class="fname">Ideias de Conteúdo</div>
374
+ <div class="fdesc">Nunca mais trave na frente da câmera. Receba 9 pautas com formato sugerido (Reels, Carrossel, Story, YouTube).</div>
375
+ <span class="farrow">→</span>
376
+ </div>
377
+ <div class="fcard" onclick="openApp('script')">
378
+ <div class="ficon" style="background:#e0e7ff">📝</div>
379
+ <div class="fname">Roteiro de Vídeo</div>
380
+ <div class="fdesc">Da ideia à gravação: receba um roteiro completo com gancho, desenvolvimento, virada e chamada pra ação, dividido por blocos.</div>
381
+ <span class="farrow">→</span>
382
+ </div>
383
+ <div class="fcard" onclick="openApp('hashtags')">
384
+ <div class="ficon" style="background:#e0f2fe">#️⃣</div>
385
+ <div class="fname">Hashtags por Tema</div>
386
+ <div class="fdesc">Digite um tópico e receba hashtags relevantes, misturando palavras-chave e tags de nicho. Clique pra copiar.</div>
387
+ <span class="farrow">→</span>
388
+ </div>
389
+ <div class="fcard" onclick="openApp('bio')">
390
+ <div class="ficon" style="background:#dcfce7">✍️</div>
391
+ <div class="fname">Bio de Perfil</div>
392
+ <div class="fdesc">Gere variações de bio otimizadas para Instagram e TikTok, com emojis, proposta de valor e chamada pra ação.</div>
393
+ <span class="farrow">→</span>
394
+ </div>
395
+ <div class="fcard" onclick="openApp('calendar')">
396
+ <div class="ficon" style="background:#ffe4e6">📅</div>
397
+ <div class="fname">Calendário de Posts</div>
398
+ <div class="fdesc">Um plano semanal de postagem pronto, com tema, formato e melhor horário para cada dia da semana.</div>
399
+ <span class="farrow">→</span>
400
+ </div>
401
+ </div>
402
+ </section>
403
+
404
+ <!-- HOW IT WORKS -->
405
+ <section class="sec" id="how" style="background:var(--bg2);border-top:1px solid var(--line);border-bottom:1px solid var(--line);max-width:none">
406
+ <div style="max-width:1120px;margin:0 auto">
407
+ <div class="sec-head">
408
+ <span class="sec-tag">Como funciona</span>
409
+ <h2 class="sec-title">Do link ao post em 3 passos</h2>
410
+ </div>
411
+ <div class="steps3">
412
+ <div class="s3"><h4>Cole ou escreva</h4><p>Um link de vídeo, um arquivo do seu PC, ou só o tema sobre o qual você quer falar.</p></div>
413
+ <div class="s3"><h4>A mágica acontece</h4><p>O CreatorKit processa localmente e gera todo o material — sem enviar nada para a nuvem.</p></div>
414
+ <div class="s3"><h4>Copie e publique</h4><p>Cada bloco tem um botão de copiar. Leve direto para o Instagram, YouTube ou onde quiser.</p></div>
415
+ </div>
416
+ </div>
417
+ </section>
418
+
419
+ <!-- WHY -->
420
+ <section class="sec" id="why">
421
+ <div class="sec-head">
422
+ <span class="sec-tag">Por quê</span>
423
+ <h2 class="sec-title">Feito para quem posta de verdade</h2>
424
+ </div>
425
+ <div class="fgrid">
426
+ <div class="fcard" style="cursor:default">
427
+ <div class="ficon" style="background:#dcfce7">🔒</div>
428
+ <div class="fname">100% privado</div>
429
+ <div class="fdesc">Roda no seu computador. Nenhum vídeo, áudio ou texto é enviado para servidores externos.</div>
430
+ </div>
431
+ <div class="fcard" style="cursor:default">
432
+ <div class="ficon" style="background:#fef3c7">⚡</div>
433
+ <div class="fname">Sem mensalidade</div>
434
+ <div class="fdesc">Esqueça assinaturas caras de IA. Aqui você gera quanto conteúdo quiser, sem limite e sem custo.</div>
435
+ </div>
436
+ <div class="fcard" style="cursor:default">
437
+ <div class="ficon" style="background:#ece8ff">🎯</div>
438
+ <div class="fname">Tudo num lugar</div>
439
+ <div class="fdesc">Pare de pular entre cinco apps diferentes. Transcrição, hooks, ideias e planejamento no mesmo painel.</div>
440
+ </div>
441
+ </div>
442
+ </section>
443
+
444
+ <!-- CTA -->
445
+ <div class="cta-band">
446
+ <div class="cta-inner">
447
+ <h2>Pronto pra criar<br>sem travar?</h2>
448
+ <p>Abra o painel e gere seu primeiro pacote de conteúdo agora mesmo.</p>
449
+ <button class="btn-lg btn-primary" onclick="openApp()" style="position:relative">Abrir CreatorKit →</button>
450
+ </div>
451
+ </div>
452
+
453
+ <footer class="foot">
454
+ <div class="foot-in">
455
+ <div class="brand"><div class="bmark">CK</div><span class="bname">CreatorKit</span></div>
456
+ <div class="foot-c">Feito para criadores · Processamento 100% local · <span id="ano"></span></div>
457
+ </div>
458
+ </footer>
459
+ </div>
460
+
461
+ <!-- ═══════════════ APP VIEW ═══════════════ -->
462
+ <div class="view" id="view-app">
463
+ <div class="app-shell">
464
+
465
+ <!-- SIDEBAR -->
466
+ <aside class="app-side">
467
+ <div class="app-side-top" onclick="goLanding()">
468
+ <div class="bmark">CK</div>
469
+ <span class="bname" style="font-size:17px">CreatorKit</span>
470
+ </div>
471
+ <nav class="tool-nav">
472
+ <div class="tn-label">Ferramentas</div>
473
+ <div class="tn on" data-tool="video"><span class="tn-ico">🎬</span> Estúdio de Vídeo</div>
474
+ <div class="tn" data-tool="hooks"><span class="tn-ico">🪝</span> Gerador de Hooks</div>
475
+ <div class="tn" data-tool="ideas"><span class="tn-ico">💡</span> Ideias de Conteúdo</div>
476
+ <div class="tn" data-tool="script"><span class="tn-ico">📝</span> Roteiro de Vídeo</div>
477
+ <div class="tn" data-tool="hashtags"><span class="tn-ico">#️⃣</span> Hashtags por Tema</div>
478
+ <div class="tn" data-tool="bio"><span class="tn-ico">✍️</span> Bio de Perfil</div>
479
+ <div class="tn" data-tool="calendar"><span class="tn-ico">📅</span> Calendário de Posts</div>
480
+ <div class="tn-label">Geral</div>
481
+ <div class="tn" onclick="goLanding()"><span class="tn-ico">←</span> Voltar ao início</div>
482
+ </nav>
483
+ </aside>
484
+
485
+ <!-- MAIN -->
486
+ <main class="app-main">
487
+ <div class="app-head">
488
+ <div class="app-h-title" id="app-title">Estúdio de Vídeo</div>
489
+ <div class="app-h-sub" id="app-sub">Transcreva e transforme qualquer vídeo em conteúdo para todas as redes.</div>
490
+ </div>
491
+ <div class="app-body">
492
+
493
+ <!-- ════ TOOL: VIDEO ════ -->
494
+ <div class="tool-pane on" id="tool-video">
495
+ <div class="card-grid">
496
+ <!-- INPUT -->
497
+ <div class="panel">
498
+ <div class="itabs">
499
+ <div class="itab on" data-for="url">🔗 Link</div>
500
+ <div class="itab" data-for="file">📂 Arquivo</div>
501
+ </div>
502
+ <div class="tpane on" id="pane-url">
503
+ <div class="fld">
504
+ <span class="fld-l">URL do vídeo</span>
505
+ <div class="urlw">
506
+ <span class="pl" id="plat-ico">🔗</span>
507
+ <input class="inp" type="url" id="url-inp" placeholder="Cole o link aqui..." autocomplete="off" spellcheck="false"/>
508
+ <button class="pbtn" id="paste-btn">Colar</button>
509
+ </div>
510
+ </div>
511
+ <div id="info-load" style="font-size:12px;color:var(--ink3);display:none">⏳ Buscando...</div>
512
+ <div class="vprev" id="vprev"><img id="vthumb" src="" alt=""/><div><div class="t" id="vtitle"></div><div class="m" id="vmeta"></div></div></div>
513
+ </div>
514
+ <div class="tpane" id="pane-file">
515
+ <div class="dz" id="dz"><div class="i">📁</div><div class="l">Arraste ou clique para selecionar</div><div class="s">MP4, MOV, MP3, WAV — até 2 GB</div></div>
516
+ <input type="file" id="fi" accept="video/*,audio/*" style="display:none"/>
517
+ <div class="fchosen" id="fcho"><span id="fname" style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"></span><button class="fx" id="fclr">✕</button></div>
518
+ </div>
519
+ <div class="row2" style="margin-top:8px">
520
+ <div class="fld" style="margin-bottom:0">
521
+ <span class="fld-l">Modelo</span>
522
+ <select class="sel" id="model-sel">
523
+ <option value="Xenova/whisper-tiny">Tiny — rápido</option>
524
+ <option value="Xenova/whisper-base">Base</option>
525
+ <option value="Xenova/whisper-small" selected>Small ✦</option>
526
+ <option value="Xenova/whisper-medium">Medium</option>
527
+ <option value="Xenova/whisper-large-v3">Large</option>
528
+ </select>
529
+ </div>
530
+ <div class="fld" style="margin-bottom:0">
531
+ <span class="fld-l">Idioma</span>
532
+ <select class="sel" id="lang-sel">
533
+ <option value="auto">🌐 Auto</option><option value="pt">🇧🇷 PT</option><option value="en">🇺🇸 EN</option>
534
+ <option value="es">🇪🇸 ES</option><option value="fr">🇫🇷 FR</option><option value="de">🇩🇪 DE</option>
535
+ <option value="it">🇮🇹 IT</option><option value="ja">🇯🇵 JA</option>
536
+ </select>
537
+ </div>
538
+ </div>
539
+ <div class="fld" style="margin-top:12px">
540
+ <span class="fld-l">Modo</span>
541
+ <div class="itabs" style="margin-bottom:0">
542
+ <div class="itab on" data-task="transcribe">📝 Transcrever</div>
543
+ <div class="itab" data-task="translate">🌍 Traduzir → EN</div>
544
+ </div>
545
+ </div>
546
+ <button class="run-btn" id="proc-btn" style="margin-top:16px">⚡ Processar vídeo</button>
547
+ <div class="pg" id="pg">
548
+ <div class="steps">
549
+ <div class="step" id="s0"><div class="sc"></div><div class="slb">Baixar</div></div>
550
+ <div class="step" id="s1"><div class="sc"></div><div class="slb">Extrair</div></div>
551
+ <div class="step" id="s2"><div class="sc"></div><div class="slb">Transcrever</div></div>
552
+ <div class="step" id="s3"><div class="sc"></div><div class="slb">Gerar</div></div>
553
+ </div>
554
+ </div>
555
+ <div class="errbox" id="errbox"></div>
556
+ <button class="btn pri" id="demo-btn" style="width:100%;margin-top:12px;justify-content:center;padding:10px">▶ Ver demonstração de exemplo</button>
557
+ </div>
558
+
559
+ <!-- OUTPUT -->
560
+ <div class="panel" style="padding:0;overflow:hidden">
561
+ <div class="empty" id="v-empty">
562
+ <div class="g">✦</div>
563
+ <h3>Seu conteúdo aparece aqui</h3>
564
+ <p>Processe um vídeo ou clique em "Ver demonstração" para ver as 9 abas de conteúdo geradas.</p>
565
+ </div>
566
+ <div class="outc" id="outc" style="padding:20px">
567
+ <div class="sbar" id="sbar"></div>
568
+ <div class="otnav">
569
+ <div class="otab on" data-tab="transcript">📝 Texto</div>
570
+ <div class="otab" data-tab="summary">✨ Resumo</div>
571
+ <div class="otab" data-tab="instagram">📸 Instagram</div>
572
+ <div class="otab" data-tab="youtube">▶️ YouTube</div>
573
+ <div class="otab" data-tab="twitter">🐦 Twitter</div>
574
+ <div class="otab" data-tab="linkedin">💼 LinkedIn</div>
575
+ <div class="otab" data-tab="blog">📰 Blog</div>
576
+ <div class="otab" data-tab="hashtags">#️⃣ Tags</div>
577
+ <div class="otab" data-tab="downloads">⬇️ Baixar</div>
578
+ </div>
579
+ <div class="opanels">
580
+ <div class="opanel on" id="panel-transcript">
581
+ <div class="srchw"><span class="sico">🔍</span><input class="srchi" id="srch" placeholder="Buscar..."/><span class="scnt" id="scnt"></span></div>
582
+ <div class="cb">
583
+ <div class="cbh"><span class="cbt">Transcrição</span><div class="cba"><button class="btn pri" id="cpy-tr">📋 Copiar</button><button class="btn" id="exp-txt">.txt</button><button class="btn" id="exp-srt">.srt</button><button class="btn" id="exp-json">.json</button></div></div>
584
+ <div class="cbb mono" id="trbody"></div>
585
+ <div class="segtog" id="segtog"><span class="cbt">Segmentos com tempo</span><span class="segarr" id="segarr">▼</span></div>
586
+ <div class="seglist" id="seglist"></div>
587
+ </div>
588
+ </div>
589
+ <div class="opanel" id="panel-summary">
590
+ <div class="cb"><div class="cbh"><span class="cbt">Resumo</span><div class="cba"><button class="btn pri" onclick="cpEl('sumbody',this)">📋 Copiar</button></div></div><div class="cbb" id="sumbody"></div></div>
591
+ <div class="cb"><div class="cbh"><span class="cbt">Palavras-chave</span></div><div class="cloud" id="kwcloud"></div></div>
592
+ </div>
593
+ <div class="opanel" id="panel-instagram"><div class="cb"><div class="cbh"><span class="cbt">Caption Instagram</span><div class="cba"><button class="btn pri" onclick="cpEl('igbody',this)">📋 Copiar</button></div></div><div class="cbb" id="igbody"></div></div></div>
594
+ <div class="opanel" id="panel-youtube">
595
+ <div class="cb"><div class="cbh"><span class="cbt">Sugestões de título</span></div><div id="yttitles"></div></div>
596
+ <div class="cb"><div class="cbh"><span class="cbt">Descrição + capítulos</span><div class="cba"><button class="btn pri" onclick="cpEl('ytdesc',this)">📋 Copiar</button></div></div><div class="cbb mono" id="ytdesc"></div></div>
597
+ <div class="cb"><div class="cbh"><span class="cbt">Tags</span><div class="cba"><button class="btn sm" onclick="cpEl('yttags',this)">📋</button></div></div><div class="cbb" id="yttags" style="color:var(--ink2);font-size:13px"></div></div>
598
+ </div>
599
+ <div class="opanel" id="panel-twitter">
600
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px"><span class="cbt">Thread Twitter/X</span><button class="btn pri" id="cpy-thread">📋 Copiar thread</button></div>
601
+ <div id="twlist"></div>
602
+ </div>
603
+ <div class="opanel" id="panel-linkedin"><div class="cb"><div class="cbh"><span class="cbt">Post LinkedIn</span><div class="cba"><button class="btn pri" onclick="cpEl('libody',this)">📋 Copiar</button></div></div><div class="cbb" id="libody"></div></div></div>
604
+ <div class="opanel" id="panel-blog"><div class="cb"><div class="cbh"><span class="cbt">Post de Blog (Markdown)</span><div class="cba"><button class="btn pri" onclick="cpEl('blbody',this)">📋 Copiar</button><button class="btn" id="exp-md">.md</button></div></div><div class="cbb mono" id="blbody"></div></div></div>
605
+ <div class="opanel" id="panel-hashtags">
606
+ <div class="cb"><div class="cbh"><span class="cbt">Hashtags</span><div class="cba"><button class="btn pri" id="cpy-tags">📋 Copiar todas</button></div></div><div class="cloud" id="htcloud"></div></div>
607
+ </div>
608
+ <div class="opanel" id="panel-downloads">
609
+ <div class="cb"><div class="cbh"><span class="cbt">Mídia original</span></div>
610
+ <div class="dlg" id="dlmedia">
611
+ <div class="dlc"><div class="i">🎞️</div><div class="n">Vídeo MP4</div><div class="d">Baixar o vídeo original</div><button class="btn pri" id="dl-vid" style="margin-top:4px;justify-content:center">Baixar vídeo</button></div>
612
+ <div class="dlc"><div class="i">🎵</div><div class="n">Áudio MP3</div><div class="d">Áudio em 192kbps</div><button class="btn" id="dl-aud" style="margin-top:4px;justify-content:center">Baixar áudio</button></div>
613
+ </div>
614
+ <div id="dlnote" style="display:none;padding:16px;font-size:13px;color:var(--ink3)">Arquivo local — download de mídia indisponível.</div>
615
+ </div>
616
+ <div class="cb"><div class="cbh"><span class="cbt">Exportar transcrição</span></div>
617
+ <div class="dlg">
618
+ <div class="dlc"><div class="i">📄</div><div class="n">.txt</div><div class="d">Texto puro</div><button class="btn" onclick="expTxt()" style="margin-top:4px;justify-content:center">Baixar</button></div>
619
+ <div class="dlc"><div class="i">🎬</div><div class="n">.srt</div><div class="d">Legenda c/ tempo</div><button class="btn" onclick="expSrt()" style="margin-top:4px;justify-content:center">Baixar</button></div>
620
+ <div class="dlc"><div class="i">📦</div><div class="n">.json</div><div class="d">Dados + análise</div><button class="btn" onclick="expJson()" style="margin-top:4px;justify-content:center">Baixar</button></div>
621
+ <div class="dlc"><div class="i">📝</div><div class="n">.md</div><div class="d">Post de blog</div><button class="btn" onclick="expMd()" style="margin-top:4px;justify-content:center">Baixar</button></div>
622
+ </div>
623
+ </div>
624
+ </div>
625
+ </div>
626
+ </div>
627
+ </div>
628
+ </div>
629
+ </div>
630
+
631
+ <!-- ════ TOOL: HOOKS ════ -->
632
+ <div class="tool-pane" id="tool-hooks">
633
+ <div class="card-grid">
634
+ <div class="panel">
635
+ <div class="hint">🪝 <b>Hooks</b> são as primeiras frases do seu vídeo ou post. Elas decidem se a pessoa continua ou rola pra baixo.</div>
636
+ <div class="fld"><span class="fld-l">Sobre o que é o conteúdo?</span><input class="inp" id="hook-inp" placeholder="ex: marketing digital, treino em casa, finanças..."/></div>
637
+ <button class="run-btn" id="hook-btn">🪝 Gerar 10 hooks</button>
638
+ </div>
639
+ <div class="panel" style="padding:0;overflow:hidden">
640
+ <div class="empty" id="hook-empty"><div class="g">🪝</div><h3>Ganchos que prendem</h3><p>Digite um tema e gere 10 aberturas testadas para seus vídeos e posts.</p></div>
641
+ <div class="cb" id="hook-out" style="display:none;margin:0;border:none"><div class="cbh"><span class="cbt">10 hooks gerados</span><button class="btn pri" id="hook-cpy">📋 Copiar todos</button></div><div id="hook-list"></div></div>
642
+ </div>
643
+ </div>
644
+ </div>
645
+
646
+ <!-- ════ TOOL: IDEAS ════ -->
647
+ <div class="tool-pane" id="tool-ideas">
648
+ <div class="card-grid">
649
+ <div class="panel">
650
+ <div class="hint">💡 Receba pautas prontas com o <b>formato ideal</b> sugerido para cada uma.</div>
651
+ <div class="fld"><span class="fld-l">Qual é o seu nicho?</span><input class="inp" id="idea-inp" placeholder="ex: culinária fitness, viagens, tecnologia..."/></div>
652
+ <button class="run-btn" id="idea-btn">💡 Gerar 9 ideias</button>
653
+ </div>
654
+ <div class="panel" style="padding:0;overflow:hidden">
655
+ <div class="empty" id="idea-empty"><div class="g">💡</div><h3>Nunca mais trave</h3><p>Digite seu nicho e receba 9 ideias com formato sugerido (Reels, Carrossel, Story...).</p></div>
656
+ <div class="cb" id="idea-out" style="display:none;margin:0;border:none"><div class="cbh"><span class="cbt">Ideias de conteúdo</span><button class="btn pri" id="idea-cpy">📋 Copiar</button></div><div id="idea-list"></div></div>
657
+ </div>
658
+ </div>
659
+ </div>
660
+
661
+ <!-- ════ TOOL: SCRIPT ════ -->
662
+ <div class="tool-pane" id="tool-script">
663
+ <div class="card-grid">
664
+ <div class="panel">
665
+ <div class="hint">📝 Recebe um roteiro completo em blocos: <b>gancho → contexto → 3 pontos → virada → CTA</b>. Pronto pra gravar.</div>
666
+ <div class="fld"><span class="fld-l">Tema do vídeo</span><input class="inp" id="scr-inp" placeholder="ex: como economizar dinheiro, receita rápida..."/></div>
667
+ <div class="fld"><span class="fld-l">Formato</span><select class="sel" id="scr-fmt"><option value="reels">🎬 Reels / TikTok (curto)</option><option value="youtube">▶️ YouTube (com intro)</option></select></div>
668
+ <button class="run-btn" id="scr-btn">📝 Gerar roteiro</button>
669
+ </div>
670
+ <div class="panel" style="padding:0;overflow:hidden">
671
+ <div class="empty" id="scr-empty"><div class="g">📝</div><h3>Roteiro pronto pra gravar</h3><p>Digite o tema e receba um roteiro estruturado bloco a bloco, com dicas de direção.</p></div>
672
+ <div class="cb" id="scr-out" style="display:none;margin:0;border:none"><div class="cbh"><span class="cbt" id="scr-dur">Roteiro</span><button class="btn pri" id="scr-cpy">📋 Copiar roteiro</button></div><div id="scr-list"></div></div>
673
+ </div>
674
+ </div>
675
+ </div>
676
+
677
+ <!-- ════ TOOL: HASHTAGS ════ -->
678
+ <div class="tool-pane" id="tool-hashtags">
679
+ <div class="card-grid">
680
+ <div class="panel">
681
+ <div class="hint">#️⃣ Combina palavras-chave do seu tema com hashtags de nicho. <b>Clique em cada tag pra copiar.</b></div>
682
+ <div class="fld"><span class="fld-l">Tema ou descrição do post</span><textarea class="ta" id="ht-inp" placeholder="ex: dicas de produtividade para quem trabalha em casa"></textarea></div>
683
+ <button class="run-btn" id="ht-btn">#️⃣ Gerar hashtags</button>
684
+ </div>
685
+ <div class="panel" style="padding:0;overflow:hidden">
686
+ <div class="empty" id="ht-empty"><div class="g">#️⃣</div><h3>Tags relevantes</h3><p>Descreva o post e receba hashtags prontas para aumentar o alcance.</p></div>
687
+ <div class="cb" id="ht-out" style="display:none;margin:0;border:none"><div class="cbh"><span class="cbt">Hashtags</span><button class="btn pri" id="ht-cpy">📋 Copiar todas</button></div><div class="cloud" id="ht-cloud"></div></div>
688
+ </div>
689
+ </div>
690
+ </div>
691
+
692
+ <!-- ════ TOOL: BIO ════ -->
693
+ <div class="tool-pane" id="tool-bio">
694
+ <div class="card-grid">
695
+ <div class="panel">
696
+ <div class="hint">✍️ Gera <b>4 variações</b> de bio prontas para colar no seu perfil.</div>
697
+ <div class="fld"><span class="fld-l">Seu nome / marca (opcional)</span><input class="inp" id="bio-name" placeholder="ex: Ana Souza"/></div>
698
+ <div class="fld"><span class="fld-l">Seu nicho</span><input class="inp" id="bio-niche" placeholder="ex: finanças pessoais"/></div>
699
+ <div class="fld"><span class="fld-l">Chamada / link (opcional)</span><input class="inp" id="bio-cta" placeholder="ex: 👇 Baixe meu e-book grátis"/></div>
700
+ <button class="run-btn" id="bio-btn">✍️ Gerar bios</button>
701
+ </div>
702
+ <div class="panel" style="padding:0;overflow:hidden">
703
+ <div class="empty" id="bio-empty"><div class="g">✍️</div><h3>Bio que converte</h3><p>Preencha seu nicho e receba variações otimizadas com emojis e CTA.</p></div>
704
+ <div id="bio-out" style="display:none;padding:20px"></div>
705
+ </div>
706
+ </div>
707
+ </div>
708
+
709
+ <!-- ════ TOOL: CALENDAR ════ -->
710
+ <div class="tool-pane" id="tool-calendar">
711
+ <div class="card-grid">
712
+ <div class="panel">
713
+ <div class="hint">📅 Monta um <b>plano semanal</b> com tema, formato e melhor horário por dia.</div>
714
+ <div class="fld"><span class="fld-l">Seu nicho</span><input class="inp" id="cal-niche" placeholder="ex: moda sustentável"/></div>
715
+ <div class="fld"><span class="fld-l">Posts por semana</span><select class="sel" id="cal-freq"><option value="3">3 posts</option><option value="5" selected>5 posts</option><option value="7">7 posts (todo dia)</option></select></div>
716
+ <button class="run-btn" id="cal-btn">📅 Gerar calendário</button>
717
+ </div>
718
+ <div class="panel" style="padding:0;overflow:hidden">
719
+ <div class="empty" id="cal-empty"><div class="g">📅</div><h3>Planejamento pronto</h3><p>Receba um cronograma semanal completo, dia a dia, pra nunca ficar sem postar.</p></div>
720
+ <div class="cb" id="cal-out" style="display:none;margin:0;border:none"><div class="cbh"><span class="cbt">Plano da semana</span><button class="btn pri" id="cal-cpy">📋 Copiar</button></div><div id="cal-list"></div></div>
721
+ </div>
722
+ </div>
723
+ </div>
724
+
725
+ </div>
726
+ </main>
727
+ </div>
728
+ </div>
729
+
730
+ <script>
731
+ const G=id=>document.getElementById(id);
732
+ const esc=s=>(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
733
+ const fmt=s=>s==null?'?':`${Math.floor(s/60)}:${Math.floor(s%60).toString().padStart(2,'0')}`;
734
+ const pad=n=>n.toString().padStart(2,'0');
735
+ const wc=t=>t.trim().split(/\s+/).filter(Boolean).length;
736
+ function cpTxt(txt,btn,lbl){navigator.clipboard.writeText(txt).then(()=>{const o=btn.textContent;btn.textContent='✓ Copiado!';btn.classList.add('ok');setTimeout(()=>{btn.textContent=lbl||o;btn.classList.remove('ok');},1800);});}
737
+ function cpEl(id,btn){cpTxt(G(id).textContent,btn);}
738
+ function dl(blob,name){const a=Object.assign(document.createElement('a'),{href:URL.createObjectURL(blob),download:name});a.click();setTimeout(()=>URL.revokeObjectURL(a.href),5000);}
739
+ async function api(path,body){const r=await fetch(path,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});const d=await r.json();if(d.error)throw new Error(d.error);return d;}
740
+
741
+ // fill year
742
+ G('ano').textContent=new Date().getFullYear();
743
+
744
+ // ════ VIEW ROUTING ════
745
+ function openApp(tool){G('view-landing').classList.remove('on');G('view-app').classList.add('on');window.scrollTo(0,0);if(tool)selectTool(tool);}
746
+ function goLanding(){G('view-app').classList.remove('on');G('view-landing').classList.add('on');window.scrollTo(0,0);}
747
+ window.openApp=openApp;window.goLanding=goLanding;window.cpEl=cpEl;
748
+
749
+ const TOOL_META={
750
+ video:{t:'Estúdio de Vídeo',s:'Transcreva e transforme qualquer vídeo em conteúdo para todas as redes.'},
751
+ hooks:{t:'Gerador de Hooks',s:'Crie aberturas que prendem a atenção nos primeiros segundos.'},
752
+ ideas:{t:'Ideias de Conteúdo',s:'Pautas prontas com o formato ideal para cada uma.'},
753
+ script:{t:'Roteiro de Vídeo',s:'Roteiro completo em blocos, da abertura à chamada pra ação.'},
754
+ hashtags:{t:'Hashtags por Tema',s:'Gere hashtags relevantes a partir de qualquer tópico.'},
755
+ bio:{t:'Bio de Perfil',s:'Variações de bio otimizadas para seu perfil.'},
756
+ calendar:{t:'Calendário de Posts',s:'Um plano semanal de postagem pronto pra usar.'},
757
+ };
758
+ function selectTool(name){
759
+ document.querySelectorAll('.tn[data-tool]').forEach(t=>t.classList.toggle('on',t.dataset.tool===name));
760
+ document.querySelectorAll('.tool-pane').forEach(p=>p.classList.remove('on'));
761
+ G(`tool-${name}`).classList.add('on');
762
+ G('app-title').textContent=TOOL_META[name].t;
763
+ G('app-sub').textContent=TOOL_META[name].s;
764
+ }
765
+ document.querySelectorAll('.tn[data-tool]').forEach(t=>t.addEventListener('click',()=>selectTool(t.dataset.tool)));
766
+
767
+ // ════════════════════ VIDEO STUDIO ════════════════════
768
+ const S={text:'',segs:[],analysis:null,url:'',vtitle:'',isFile:false,task:'transcribe',itab:'url',poll:null,file:null,segOpen:false};
769
+
770
+ const PLATS=[{re:/instagram\.com/,ico:'📸'},{re:/youtu\.?be/,ico:'▶️'},{re:/tiktok\.com/,ico:'🎵'},{re:/(twitter|x)\.com/,ico:'𝕏'},{re:/facebook\.com|fb\.watch/,ico:'📘'},{re:/twitch\.tv/,ico:'🟣'}];
771
+ const plat=u=>{for(const p of PLATS)if(p.re.test(u))return p;return{ico:'🔗'};};
772
+
773
+ document.querySelectorAll('#tool-video .itab[data-for]').forEach(t=>t.addEventListener('click',()=>{
774
+ document.querySelectorAll('#tool-video .itab[data-for]').forEach(x=>x.classList.remove('on'));
775
+ document.querySelectorAll('#tool-video .tpane').forEach(x=>x.classList.remove('on'));
776
+ t.classList.add('on');S.itab=t.dataset.for;G(`pane-${S.itab}`).classList.add('on');
777
+ }));
778
+ document.querySelectorAll('#tool-video .itab[data-task]').forEach(b=>b.addEventListener('click',()=>{
779
+ document.querySelectorAll('#tool-video .itab[data-task]').forEach(x=>x.classList.remove('on'));
780
+ b.classList.add('on');S.task=b.dataset.task;
781
+ }));
782
+
783
+ let infoT;
784
+ G('url-inp').addEventListener('input',()=>{
785
+ const u=G('url-inp').value.trim();G('plat-ico').textContent=plat(u).ico;G('vprev').classList.remove('on');
786
+ clearTimeout(infoT);if(u.startsWith('http'))infoT=setTimeout(()=>fetchInfo(u),900);
787
+ });
788
+ async function fetchInfo(u){G('info-load').style.display='block';try{const d=await api('/info',{url:u});if(d.title){G('vthumb').src=d.thumbnail||'';G('vthumb').style.display=d.thumbnail?'block':'none';G('vtitle').textContent=d.title.slice(0,75);G('vmeta').textContent=[d.uploader,d.duration?`⏱ ${fmt(d.duration)}`:''].filter(Boolean).join(' · ');G('vprev').classList.add('on');S.vtitle=d.title;}}catch{}G('info-load').style.display='none';}
789
+ G('paste-btn').addEventListener('click',async()=>{try{G('url-inp').value=await navigator.clipboard.readText();G('url-inp').dispatchEvent(new Event('input'));}catch{}});
790
+
791
+ G('dz').addEventListener('click',()=>G('fi').click());
792
+ G('dz').addEventListener('dragover',e=>{e.preventDefault();G('dz').classList.add('ov');});
793
+ G('dz').addEventListener('dragleave',()=>G('dz').classList.remove('ov'));
794
+ G('dz').addEventListener('drop',e=>{e.preventDefault();G('dz').classList.remove('ov');setFile(e.dataTransfer.files[0]);});
795
+ G('fi').addEventListener('change',()=>setFile(G('fi').files[0]));
796
+ G('fclr').addEventListener('click',()=>{S.file=null;G('fi').value='';G('fcho').classList.remove('on');});
797
+ function setFile(f){if(!f)return;S.file=f;G('fname').textContent=`📎 ${f.name} · ${(f.size/1024/1024).toFixed(1)} MB`;G('fcho').classList.add('on');}
798
+
799
+ const SMAP={downloading_ytdlp:0,downloading:0,extracting:1,transcribing:2,analyzing:3,done:'all'};
800
+ function setSteps(st){const a=SMAP[st];[0,1,2,3].forEach(i=>{const el=G(`s${i}`);el.classList.remove('ac','dn');if(a==='all')el.classList.add('dn');else if(i===a){el.classList.add('ac');for(let j=0;j<i;j++)G(`s${j}`).classList.add('dn');}});}
801
+
802
+ G('proc-btn').addEventListener('click',startProc);
803
+ G('url-inp').addEventListener('keydown',e=>{if(e.key==='Enter')startProc();});
804
+ async function startProc(){
805
+ G('errbox').style.display='none';G('outc').classList.remove('on');G('v-empty').style.display='flex';
806
+ G('pg').style.display='block';G('proc-btn').disabled=true;G('proc-btn').textContent='⏳ Processando...';
807
+ try{
808
+ let jobId;
809
+ if(S.itab==='url'){
810
+ const url=G('url-inp').value.trim();if(!url)throw new Error('Informe uma URL.');
811
+ S.url=url;S.isFile=false;setSteps('downloading');
812
+ const d=await api('/transcribe',{url,model:G('model-sel').value,language:G('lang-sel').value,task:S.task});jobId=d.job_id;
813
+ }else{
814
+ if(!S.file)throw new Error('Selecione um arquivo.');
815
+ S.url='';S.isFile=true;setSteps('extracting');
816
+ const fd=new FormData();fd.append('file',S.file);fd.append('model',G('model-sel').value);fd.append('language',G('lang-sel').value);fd.append('task',S.task);
817
+ const r=await fetch('/transcribe-file',{method:'POST',body:fd});const d=await r.json();if(d.error)throw new Error(d.error);jobId=d.job_id;
818
+ }
819
+ clearInterval(S.poll);S.poll=setInterval(()=>poll(jobId),2000);
820
+ }catch(e){showErr(e.message);}
821
+ }
822
+ async function poll(id){try{const job=await(await fetch(`/status/${id}`)).json();setSteps(job.status);if(job.status==='done'){clearInterval(S.poll);setSteps('analyzing');await build(job);}else if(job.status==='error'){clearInterval(S.poll);showErr(job.error||'Erro');}}catch{}}
823
+ async function build(job){
824
+ S.text=job.text||'';S.segs=job.segments||[];
825
+ try{const d=await api('/analyze',{text:S.text,segments:S.segs,title:S.vtitle});S.analysis=d;}catch{S.analysis=null;}
826
+ setSteps('done');G('pg').style.display='none';G('proc-btn').disabled=false;G('proc-btn').textContent='⚡ Processar vídeo';
827
+ G('v-empty').style.display='none';populate(job);G('outc').classList.add('on');switchTab('transcript');
828
+ }
829
+ function populate(job){
830
+ const c=[];if(job.language)c.push(`🌐 ${job.language.toUpperCase()}`);c.push(`📝 ${wc(S.text)} palavras`);if(S.segs.length)c.push(`⏱ ${fmt(S.segs.at(-1)?.end)}`);if(S.analysis?.stats)c.push(`📖 ~${S.analysis.stats.readingTime}min`);
831
+ G('sbar').innerHTML=c.map(x=>`<span class="schip">${esc(x)}</span>`).join('');
832
+ G('trbody').textContent=S.text;
833
+ const sl=G('seglist');sl.innerHTML='';
834
+ S.segs.forEach(s=>{const d=document.createElement('div');d.className='segitem';d.innerHTML=`<span class="segtime">${fmt(s.start)}→${fmt(s.end)}</span><span class="segtxt">${esc(s.text)}</span><button class="segcp">📋</button>`;d.querySelector('.segcp').onclick=e=>{navigator.clipboard.writeText(s.text);e.target.textContent='✓';setTimeout(()=>e.target.textContent='📋',1400);};sl.appendChild(d);});
835
+ if(S.analysis){
836
+ G('sumbody').textContent=S.analysis.summary||'';
837
+ const kc=G('kwcloud');kc.innerHTML='';(S.analysis.keywords||[]).forEach((k,i)=>{const sp=document.createElement('span');sp.className='tag kw'+(i<3?' top':'');sp.textContent=`${k.word} (${k.count})`;kc.appendChild(sp);});
838
+ G('igbody').textContent=S.analysis.instagram||'';
839
+ const yt=S.analysis.youtube||{};const tl=G('yttitles');tl.innerHTML='';(yt.titles||[]).forEach(t=>{const d=document.createElement('div');d.className='litem';d.innerHTML=`<span class="txt">${esc(t)}</span><button class="btn sm">📋</button>`;d.querySelector('button').onclick=e=>{navigator.clipboard.writeText(t);e.target.textContent='✓';setTimeout(()=>e.target.textContent='📋',1400);};tl.appendChild(d);});
840
+ G('ytdesc').textContent=yt.description||'';G('yttags').textContent=yt.tags||'';
841
+ const th=S.analysis.twitter||[];const tw=G('twlist');tw.innerHTML='';th.forEach(t=>{const d=document.createElement('div');d.className='tw';d.textContent=t;tw.appendChild(d);});
842
+ G('cpy-thread').onclick=()=>cpTxt(th.join('\n\n---\n\n'),G('cpy-thread'),'📋 Copiar thread');
843
+ G('libody').textContent=S.analysis.linkedin||'';G('blbody').textContent=S.analysis.blog||'';
844
+ const tags=S.analysis.hashtags||[];const hc=G('htcloud');hc.innerHTML='';tags.forEach(tag=>{const sp=document.createElement('span');sp.className='tag ht';sp.textContent=tag;sp.onclick=()=>{navigator.clipboard.writeText(tag);sp.classList.add('cp');setTimeout(()=>sp.classList.remove('cp'),1400);};hc.appendChild(sp);});
845
+ G('cpy-tags').onclick=()=>cpTxt(tags.join(' '),G('cpy-tags'),'📋 Copiar todas');
846
+ }
847
+ const has=!S.isFile&&S.url;G('dlmedia').style.display=has?'grid':'none';G('dlnote').style.display=has?'none':'block';
848
+ }
849
+ document.querySelectorAll('#tool-video .otab').forEach(t=>t.addEventListener('click',()=>switchTab(t.dataset.tab)));
850
+ function switchTab(n){document.querySelectorAll('#tool-video .otab').forEach(t=>t.classList.toggle('on',t.dataset.tab===n));document.querySelectorAll('#tool-video .opanel').forEach(p=>p.classList.remove('on'));const p=G(`panel-${n}`);if(p)p.classList.add('on');}
851
+ let sT;G('srch').addEventListener('input',()=>{clearTimeout(sT);sT=setTimeout(()=>{const q=G('srch').value.trim();if(!q){G('trbody').textContent=S.text;G('scnt').textContent='';return;}const re=new RegExp(`(${q.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')})`,'gi');G('trbody').innerHTML=S.text.split(re).map(p=>re.test(p)?`<mark>${p}</mark>`:esc(p)).join('');const m=G('trbody').querySelectorAll('mark');G('scnt').textContent=m.length?`${m.length} result.`:'0';},200);});
852
+ G('segtog').addEventListener('click',()=>{S.segOpen=!S.segOpen;G('seglist').style.display=S.segOpen?'block':'none';G('segarr').classList.toggle('op',S.segOpen);});
853
+ G('cpy-tr').addEventListener('click',()=>cpTxt(S.text,G('cpy-tr'),'📋 Copiar'));
854
+ function toSrt(s){const h=Math.floor(s/3600),m=Math.floor((s%3600)/60),sec=Math.floor(s%60),ms=Math.round((s%1)*1000);return`${pad(h)}:${pad(m)}:${pad(sec)},${ms.toString().padStart(3,'0')}`;}
855
+ function expTxt(){dl(new Blob([S.text],{type:'text/plain;charset=utf-8'}),'transcricao.txt');}
856
+ function expSrt(){dl(new Blob([S.segs.map((s,i)=>`${i+1}\n${toSrt(s.start)} --> ${toSrt(s.end)}\n${s.text}\n`).join('\n')],{type:'text/plain;charset=utf-8'}),'transcricao.srt');}
857
+ function expJson(){dl(new Blob([JSON.stringify({text:S.text,segments:S.segs,analysis:S.analysis},null,2)],{type:'application/json'}),'creatorkit.json');}
858
+ function expMd(){dl(new Blob([S.analysis?.blog||S.text],{type:'text/plain;charset=utf-8'}),'post.md');}
859
+ window.expTxt=expTxt;window.expSrt=expSrt;window.expJson=expJson;window.expMd=expMd;
860
+ G('exp-txt').onclick=expTxt;G('exp-srt').onclick=expSrt;G('exp-json').onclick=expJson;G('exp-md').onclick=expMd;
861
+ async function mediaDl(ep,fn,btn,lbl){btn.classList.add('ld');btn.textContent='⏳ Baixando...';try{const r=await fetch(ep,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url:S.url})});if(!r.ok)throw new Error((await r.json()).error);dl(await r.blob(),fn);btn.textContent='✓ Pronto!';}catch(e){btn.textContent='⚠️ Erro';showErr(e.message);}setTimeout(()=>{btn.textContent=lbl;btn.classList.remove('ld');},2500);}
862
+ G('dl-vid').onclick=()=>mediaDl('/download-video','video.mp4',G('dl-vid'),'Baixar vídeo');
863
+ G('dl-aud').onclick=()=>mediaDl('/download-audio','audio.mp3',G('dl-aud'),'Baixar áudio');
864
+ function showErr(m){G('proc-btn').disabled=false;G('proc-btn').textContent='⚡ Processar vídeo';G('pg').style.display='none';G('errbox').style.display='block';G('errbox').innerHTML=`<strong>⚠️ Erro</strong><br>${esc(m)}`;}
865
+
866
+ // demo
867
+ G('demo-btn').addEventListener('click',async()=>{
868
+ const b=G('demo-btn');b.classList.add('ld');b.textContent='⏳ Gerando...';
869
+ const t='Hoje eu vou te ensinar as três estratégias que mudaram completamente o meu Instagram. A primeira coisa que você precisa entender é que consistência vence talento. Postar todos os dias no mesmo horário treina o algoritmo a entregar o seu conteúdo. A segunda estratégia é dominar os primeiros três segundos do seu Reels, porque é nesse momento que a pessoa decide se vai continuar assistindo. Use uma pergunta forte ou um gancho visual. A terceira e mais importante é responder todos os comentários nos primeiros trinta minutos depois de postar. Isso aumenta o engajamento e mostra para a plataforma que o seu conteúdo gera conversa.';
870
+ const segs=[{start:0,end:6,text:'Hoje eu vou te ensinar as três estratégias que mudaram meu Instagram.'},{start:6,end:14,text:'A primeira coisa é que consistência vence talento.'},{start:14,end:24,text:'A segunda é dominar os primeiros três segundos do Reels.'},{start:24,end:34,text:'A terceira é responder comentários nos primeiros trinta minutos.'}];
871
+ try{const d=await api('/analyze',{text:t,segments:segs,title:'3 estratégias para crescer no Instagram'});S.text=t;S.segs=segs;S.analysis=d;S.url='https://instagram.com/reel/exemplo';S.isFile=false;G('v-empty').style.display='none';populate({language:'pt'});G('outc').classList.add('on');switchTab('transcript');}catch(e){showErr(e.message);}
872
+ b.classList.remove('ld');b.textContent='▶ Ver demonstração de exemplo';
873
+ });
874
+
875
+ // ════════════════════ HOOKS ════════════════════
876
+ G('hook-btn').addEventListener('click',async()=>{
877
+ const v=G('hook-inp').value.trim();if(!v)return G('hook-inp').focus();
878
+ const b=G('hook-btn');b.disabled=true;b.textContent='⏳ Gerando...';
879
+ try{const d=await api('/generate',{type:'hooks',topic:v});
880
+ G('hook-empty').style.display='none';G('hook-out').style.display='block';
881
+ const l=G('hook-list');l.innerHTML='';
882
+ d.hooks.forEach((h,i)=>{const div=document.createElement('div');div.className='litem';div.innerHTML=`<span class="num">${i+1}</span><span class="txt">${esc(h)}</span><button class="btn sm">📋</button>`;div.querySelector('button').onclick=e=>{navigator.clipboard.writeText(h);e.target.textContent='✓';setTimeout(()=>e.target.textContent='📋',1400);};l.appendChild(div);});
883
+ G('hook-cpy').onclick=()=>cpTxt(d.hooks.join('\n'),G('hook-cpy'),'📋 Copiar todos');
884
+ }catch(e){alert(e.message);}b.disabled=false;b.textContent='🪝 Gerar 10 hooks';
885
+ });
886
+ G('hook-inp').addEventListener('keydown',e=>{if(e.key==='Enter')G('hook-btn').click();});
887
+
888
+ // ════════════════════ IDEAS ════════════════════
889
+ G('idea-btn').addEventListener('click',async()=>{
890
+ const v=G('idea-inp').value.trim();if(!v)return G('idea-inp').focus();
891
+ const b=G('idea-btn');b.disabled=true;b.textContent='⏳ Gerando...';
892
+ try{const d=await api('/generate',{type:'ideas',niche:v});
893
+ G('idea-empty').style.display='none';G('idea-out').style.display='block';
894
+ const l=G('idea-list');l.innerHTML='';
895
+ d.ideas.forEach(it=>{const div=document.createElement('div');div.className='litem';div.innerHTML=`<span class="num">${it.icon}</span><span class="txt">${esc(it.title)}</span><span class="fmt-badge">${esc(it.fmt)}</span>`;l.appendChild(div);});
896
+ G('idea-cpy').onclick=()=>cpTxt(d.ideas.map(i=>`• ${i.title} (${i.fmt})`).join('\n'),G('idea-cpy'),'📋 Copiar');
897
+ }catch(e){alert(e.message);}b.disabled=false;b.textContent='💡 Gerar 9 ideias';
898
+ });
899
+ G('idea-inp').addEventListener('keydown',e=>{if(e.key==='Enter')G('idea-btn').click();});
900
+
901
+ // ════════════════════ SCRIPT ════════════════════
902
+ G('scr-btn').addEventListener('click',async()=>{
903
+ const v=G('scr-inp').value.trim();if(!v)return G('scr-inp').focus();
904
+ const b=G('scr-btn');b.disabled=true;b.textContent='⏳ Gerando...';
905
+ try{const d=await api('/generate',{type:'script',topic:v,format:G('scr-fmt').value});
906
+ G('scr-empty').style.display='none';G('scr-out').style.display='block';
907
+ G('scr-dur').textContent=`Roteiro · ~${d.estSeconds}s de vídeo`;
908
+ const l=G('scr-list');l.innerHTML='';
909
+ d.sections.forEach(s=>{
910
+ const div=document.createElement('div');div.style.cssText='padding:14px 16px;border-bottom:1px solid var(--line)';
911
+ div.innerHTML=`<div style="display:flex;justify-content:space-between;align-items:center;gap:8px;margin-bottom:6px"><span style="font-weight:700;font-size:13px;color:var(--acc-d)">${esc(s.label)}</span><button class="btn sm">📋</button></div><div style="font-size:14px;line-height:1.6;margin-bottom:5px">${esc(s.text)}</div><div style="font-size:12px;color:var(--ink3);font-style:italic">💡 ${esc(s.tip)}</div>`;
912
+ div.querySelector('button').onclick=e=>{navigator.clipboard.writeText(s.text);e.target.textContent='✓';setTimeout(()=>e.target.textContent='📋',1400);};
913
+ l.appendChild(div);
914
+ });
915
+ G('scr-cpy').onclick=()=>cpTxt(d.full,G('scr-cpy'),'📋 Copiar roteiro');
916
+ }catch(e){alert(e.message);}b.disabled=false;b.textContent='📝 Gerar roteiro';
917
+ });
918
+ G('scr-inp').addEventListener('keydown',e=>{if(e.key==='Enter')G('scr-btn').click();});
919
+
920
+ // ════════════════════ HASHTAGS ════════════════════
921
+ G('ht-btn').addEventListener('click',async()=>{
922
+ const v=G('ht-inp').value.trim();if(!v)return G('ht-inp').focus();
923
+ const b=G('ht-btn');b.disabled=true;b.textContent='⏳ Gerando...';
924
+ try{const d=await api('/generate',{type:'hashtags',topic:v});
925
+ G('ht-empty').style.display='none';G('ht-out').style.display='block';
926
+ const c=G('ht-cloud');c.innerHTML='';
927
+ d.hashtags.forEach(tag=>{const sp=document.createElement('span');sp.className='tag ht';sp.textContent=tag;sp.onclick=()=>{navigator.clipboard.writeText(tag);sp.classList.add('cp');setTimeout(()=>sp.classList.remove('cp'),1400);};c.appendChild(sp);});
928
+ G('ht-cpy').onclick=()=>cpTxt(d.hashtags.join(' '),G('ht-cpy'),'📋 Copiar todas');
929
+ }catch(e){alert(e.message);}b.disabled=false;b.textContent='#️⃣ Gerar hashtags';
930
+ });
931
+
932
+ // ════════════════════ BIO ════════════════════
933
+ G('bio-btn').addEventListener('click',async()=>{
934
+ const niche=G('bio-niche').value.trim();if(!niche)return G('bio-niche').focus();
935
+ const b=G('bio-btn');b.disabled=true;b.textContent='⏳ Gerando...';
936
+ try{const d=await api('/generate',{type:'bio',name:G('bio-name').value.trim(),niche,cta:G('bio-cta').value.trim()});
937
+ G('bio-empty').style.display='none';const o=G('bio-out');o.style.display='block';o.innerHTML='';
938
+ d.bios.forEach(bio=>{const div=document.createElement('div');div.className='biocard';div.innerHTML=`<button class="btn sm cp">📋</button>${esc(bio)}`;div.querySelector('.cp').onclick=e=>{navigator.clipboard.writeText(bio);e.target.textContent='✓';setTimeout(()=>e.target.textContent='📋',1400);};o.appendChild(div);});
939
+ }catch(e){alert(e.message);}b.disabled=false;b.textContent='✍️ Gerar bios';
940
+ });
941
+
942
+ // ════════════════════ CALENDAR ════════════════════
943
+ G('cal-btn').addEventListener('click',async()=>{
944
+ const niche=G('cal-niche').value.trim();if(!niche)return G('cal-niche').focus();
945
+ const b=G('cal-btn');b.disabled=true;b.textContent='⏳ Gerando...';
946
+ try{const d=await api('/generate',{type:'calendar',niche,perWeek:G('cal-freq').value});
947
+ G('cal-empty').style.display='none';G('cal-out').style.display='block';
948
+ const l=G('cal-list');l.innerHTML='';
949
+ d.plan.forEach(p=>{const div=document.createElement('div');div.className='litem';div.innerHTML=`<span class="num" style="width:auto;font-size:.85rem;color:var(--acc-d);font-weight:700;min-width:62px">${esc(p.day)}</span><span class="txt"><b>${p.icon} ${esc(p.theme)}</b><br><span style="font-size:12px;color:var(--ink3)">⏰ ${esc(p.best)}</span></span><span class="fmt-badge">${esc(p.fmt)}</span>`;l.appendChild(div);});
950
+ G('cal-cpy').onclick=()=>cpTxt(d.plan.map(p=>`${p.day}: ${p.theme} [${p.fmt}] — ${p.best}`).join('\n'),G('cal-cpy'),'📋 Copiar');
951
+ }catch(e){alert(e.message);}b.disabled=false;b.textContent='📅 Gerar calendário';
952
+ });
953
+ </script>
954
+ </body>
955
+ </html>
whisper_worker.js ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Runs as: node whisper_worker.js <audioPath> <model> <language>
2
+ const [, , audioPath, modelName, language, task = 'transcribe'] = process.argv;
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { execFileSync } = require('child_process');
6
+ const ffmpegStatic = require('ffmpeg-static');
7
+
8
+ async function main() {
9
+ const { pipeline, env } = await import('@xenova/transformers');
10
+
11
+ env.cacheDir = process.env.XENOVA_CACHE_DIR || path.join(__dirname, '.cache');
12
+ // Suppress verbose ONNX logs
13
+ env.backends.onnx.logLevel = 'error';
14
+
15
+ process.stderr.write(`Carregando modelo ${modelName}...\n`);
16
+
17
+ const transcriber = await pipeline('automatic-speech-recognition', modelName);
18
+
19
+ process.stderr.write('Convertendo áudio para PCM...\n');
20
+
21
+ // Convert audio to raw 32-bit float PCM (16kHz mono) using ffmpeg
22
+ const rawPath = audioPath + '.f32le';
23
+ execFileSync(ffmpegStatic, [
24
+ '-i', audioPath,
25
+ '-ar', '16000',
26
+ '-ac', '1',
27
+ '-f', 'f32le',
28
+ '-y', rawPath,
29
+ ], { stdio: 'pipe' });
30
+
31
+ const rawBuffer = fs.readFileSync(rawPath);
32
+ fs.unlink(rawPath, () => {});
33
+
34
+ // Build Float32Array from raw PCM bytes
35
+ const audioData = new Float32Array(
36
+ rawBuffer.buffer,
37
+ rawBuffer.byteOffset,
38
+ rawBuffer.byteLength / 4
39
+ );
40
+
41
+ process.stderr.write('Transcrevendo...\n');
42
+
43
+ const opts = {
44
+ return_timestamps: true,
45
+ chunk_length_s: 30,
46
+ stride_length_s: 5,
47
+ };
48
+ if (language && language !== 'auto') opts.language = language;
49
+ opts.task = task === 'translate' ? 'translate' : 'transcribe';
50
+
51
+ const result = await transcriber(audioData, opts);
52
+
53
+ const segments = (result.chunks || []).map(c => ({
54
+ start: c.timestamp?.[0] ?? 0,
55
+ end: c.timestamp?.[1] ?? 0,
56
+ text: c.text.trim(),
57
+ }));
58
+
59
+ process.stdout.write(JSON.stringify({
60
+ text: result.text.trim(),
61
+ language: language !== 'auto' ? language : '',
62
+ segments,
63
+ }));
64
+ }
65
+
66
+ main().catch(e => {
67
+ process.stderr.write('ERRO: ' + e.message + '\n');
68
+ process.exit(1);
69
+ });