RaiSantos commited on
Commit
8003f56
·
verified ·
1 Parent(s): 65c4d2f

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +426 -18
index.html CHANGED
@@ -1,19 +1,427 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="pt-BR">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Gerador VSL Ultra</title>
6
+ <!-- Performance: preconnect + swap for fonts -->
7
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap" rel="stylesheet">
10
+ <style>
11
+ * { box-sizing: border-box; }
12
+ body {
13
+ margin: 0; padding: 0;
14
+ font-family: 'Open Sans', sans-serif;
15
+ background-color: #f9fafb;
16
+ color: #111;
17
+ }
18
+ header {
19
+ background: linear-gradient(135deg, #111827, #1f2937);
20
+ color: white;
21
+ padding: 25px;
22
+ font-size: 28px;
23
+ font-weight: bold;
24
+ text-align: center;
25
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
26
+ }
27
+ .container {
28
+ max-width: 1200px;
29
+ margin: 40px auto;
30
+ padding: 40px;
31
+ background: white;
32
+ border-radius: 16px;
33
+ box-shadow: 0 10px 30px rgba(0,0,0,0.08);
34
+ }
35
+ .input-group {
36
+ margin-bottom: 25px;
37
+ transition: all 0.3s ease;
38
+ }
39
+ .input-group:hover { transform: translateY(-2px); }
40
+ label {
41
+ font-weight: 600;
42
+ display: block;
43
+ margin-bottom: 8px;
44
+ color: #374151;
45
+ }
46
+ input, button, audio {
47
+ width: 100%;
48
+ padding: 12px;
49
+ font-size: 16px;
50
+ border-radius: 8px;
51
+ border: 2px solid #e5e7eb;
52
+ transition: all 0.3s ease;
53
+ }
54
+ input:hover, input:focus { border-color: #2563eb; outline: none; }
55
+ button {
56
+ background: linear-gradient(135deg, #2563eb, #1d4ed8);
57
+ color: white;
58
+ border: none;
59
+ cursor: pointer;
60
+ margin-top: 10px;
61
+ font-weight: 600;
62
+ text-transform: uppercase;
63
+ letter-spacing: 0.5px;
64
+ transition: all 0.3s ease;
65
+ }
66
+ button:hover:not(:disabled) {
67
+ transform: translateY(-2px);
68
+ box-shadow: 0 4px 12px rgba(37, 99, 235, 0.2);
69
+ }
70
+ button:disabled { opacity: 0.5; cursor: not-allowed; }
71
+ .preview {
72
+ margin-top: 40px;
73
+ border: 2px dashed #cbd5e1;
74
+ border-radius: 12px;
75
+ padding: 60px 40px;
76
+ font-size: 42px;
77
+ font-family: 'Open Sans', sans-serif;
78
+ text-align: center;
79
+ white-space: pre-line;
80
+ min-height: 600px;
81
+ background: white;
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ line-height: 1.4;
86
+ transition: all 0.3s ease;
87
+ position: relative; /* ensure .slide-counter positions correctly */
88
+ }
89
+
90
+ .preview p { max-width: 1000px; margin: 0 auto; animation: fadeIn 0.3s ease-in-out; }
91
+
92
+ .preview.fullscreen {
93
+ position: fixed;
94
+ top: 0;
95
+ left: 0;
96
+ width: 100vw;
97
+ height: 100vh;
98
+ margin: 0;
99
+ padding: 40px;
100
+ border: none;
101
+ border-radius: 0;
102
+ background: white;
103
+ color: black;
104
+ font-size: 64px;
105
+ z-index: 9999;
106
+ display: flex;
107
+ align-items: center;
108
+ justify-content: center;
109
+ }
110
+
111
+ .preview.fullscreen p { max-width: 1600px; line-height: 1.4; white-space: pre-line; }
112
+
113
+ .preview.fullscreen.hd { width: 1920px; height: 1080px; transform-origin: top left; }
114
+ .preview.fullscreen.hd p { max-width: 1720px; }
115
+
116
+ .bold-black { font-weight: 700; color: black; }
117
+ .bold-red { font-weight: 700; color: #dc2626; }
118
+
119
+ .button-group { display: flex; gap: 15px; }
120
+ .button-group button { flex: 1; padding: 15px; }
121
+
122
+ /* Animações */
123
+ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
124
+
125
+ /* Status indicators */
126
+ .status-bar {
127
+ display: flex;
128
+ gap: 20px;
129
+ margin-top: 20px;
130
+ padding: 15px;
131
+ background: #f8fafc;
132
+ border-radius: 8px;
133
+ font-size: 14px;
134
+ }
135
+ .status-item { display: flex; align-items: center; gap: 8px; }
136
+ .status-dot { width: 8px; height: 8px; border-radius: 50%; background: #cbd5e1; }
137
+ .status-dot.active { background: #22c55e; }
138
+
139
+ /* Contador de slides */
140
+ .slide-counter {
141
+ position: absolute;
142
+ top: 20px;
143
+ right: 20px;
144
+ background: rgba(0,0,0,0.7);
145
+ color: white;
146
+ padding: 8px 16px;
147
+ border-radius: 20px;
148
+ font-size: 14px;
149
+ font-weight: 600;
150
+ }
151
+ /* ✅ Esconder contador em tela cheia (inclusive 1080p) */
152
+ .preview.fullscreen .slide-counter { display: none !important; }
153
+ </style>
154
+ </head>
155
+ <body>
156
+ <header>Gerador de VSL Ultra Profissional</header>
157
+ <div class="container">
158
+ <div class="input-group">
159
+ <label for="jsonFile">📄 Importar Transcrição (JSON):</label>
160
+ <input type="file" id="jsonFile" accept=".json" />
161
+ </div>
162
+ <div class="input-group">
163
+ <label for="audio">🎤 Importar Narração (Áudio):</label>
164
+ <input type="file" id="audio" accept="audio/*" />
165
+ </div>
166
+ <div class="input-group">
167
+ <audio controls id="player"></audio>
168
+ </div>
169
+ <div class="status-bar">
170
+ <div class="status-item">
171
+ <div id="transcriptionStatus" class="status-dot"></div>
172
+ <span>Transcrição</span>
173
+ </div>
174
+ <div class="status-item">
175
+ <div id="audioStatus" class="status-dot"></div>
176
+ <span>Áudio</span>
177
+ </div>
178
+ <div class="status-item">
179
+ <div id="syncStatus" class="status-dot"></div>
180
+ <span>Sincronização</span>
181
+ </div>
182
+ </div>
183
+ <div class="input-group">
184
+ <div class="button-group">
185
+ <button onclick="startSync()" id="startButton" disabled>▶️ Iniciar Apresentação</button>
186
+ <button onclick="toggleFullscreen()" id="fullscreenButton">🔲 Modo Tela Cheia</button>
187
+ <button onclick="toggle1080p()" id="hdButton">📺 Modo 1080p</button>
188
+ </div>
189
+ </div>
190
+ <div class="preview" id="slidePreview">
191
+ <p>Carregue a transcrição e o áudio para começar...</p>
192
+ <div class="slide-counter" id="slideCounter" style="display: none;">1 / 1</div>
193
+ </div>
194
+ </div>
195
+
196
+ <script>
197
+ const player = document.getElementById("player");
198
+ const preview = document.getElementById("slidePreview").querySelector("p");
199
+ const slideCounter = document.getElementById("slideCounter");
200
+ const transcriptionStatus = document.getElementById("transcriptionStatus");
201
+ const audioStatus = document.getElementById("audioStatus");
202
+ const syncStatus = document.getElementById("syncStatus");
203
+ const startButton = document.getElementById("startButton");
204
+ let slides = [];
205
+ let audioReady = false;
206
+ let isPlaying = false;
207
+ let hasStarted = false; // ✅ controla o primeiro start
208
+ let currentSlideIndex = 0;
209
+
210
+ // 🔒 Sanitize para evitar XSS em textos vindos do JSON
211
+ function escapeHtml(str) {
212
+ return String(str).replace(/[&<>"']/g, (m) => ({
213
+ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
214
+ })[m]);
215
+ }
216
+
217
+ const importantWords = new Set([
218
+ 'DOR','PESO','VERGONHA','TRISTEZA','SOFRIMENTO','CULPA','FRACASSO','LIMITE','BALANÇA','FOME','FRACA','TRANCAR','ROUBEI','FUGIR','TRISTE','COMPULSÃO','AUTOESTIMA','REALIDADE','ESCONDER','MENTIRA','DIETAS','EFEITO SANFONA','EXCESSO','RESULTADO','ESFORÇO','ENERGIA','MUDANÇA','ESCOLHA','RENASCIMENTO','ESPERANÇA','CONQUISTA','TRANSFORMAÇÃO','CORAGEM','SOLUÇÃO','FUNCIONA','EMAGRECER','ALIMENTAR','VIDA','SAÚDE','LIBERDADE','VERDADE','MILAGRE','EMOÇÃO','SUPORTE','JEJUM','PERSONAL','METABOLISMO','SAUDÁVEL','LEVE','SE OLHAR','VER','SORRIR','ORGULHO','MERECE','BRASILEIRA','REAL','CAMINHO','PASSO','PLANO','REFEIÇÕES','PRAIA','ESCONDERIJO','CONSCIÊNCIA','RESPOSTA','DESAFIO','COBRIR','RECOMEÇO','REJEIÇÃO','RECOMPENSA','DECISÃO','ACEITAÇÃO','VITÓRIA','SUPERAÇÃO','MOTIVAÇÃO','PROGRESSO','MUDOU MINHA VIDA','TRANSFORMOU MEU CORPO','NADA DAVA CERTO','DESISTIR','SOZINHA','ANSIEDADE','INSEGURANÇA','35 ANOS','9,3 QUILOS','20 QUILOS','2 QUILOS','55 CENTAVOS','53 CENTAVOS','R$55','R$197','PARABÉNS','CETOX','PERDER','QUEIMANDO','DELICIOSAS','EXTREMAMENTE','RESPONSABILIDADE','ATENÇÃO','URGENTE','INDISPONÍVEL','MANTER','PERDER','DESCOBRI','ÚNICA','ARMADURA','EFEITO','SANFONA','CERTEZA','GARANTIR','PESO','SEMPRE','FESTAS','VIAJANDO','CAÓTICAS','MÃOS','SEU','PARA','SEMPRE','COMER','ANIVERSÁRIOS','SABOTAR','TUDO','ATERRORIZAVA','NOITES','PROCESSO','EMAGRECIMENTO','ADIANTARIA','RECUPERAR','MESES','MULHERES','BRASILEIRAS','DIETA','PESQUISA','UNIVERSIDADE','SÃO','PAULO','BALANÇA','SUBIR','RESISTIR','MOMENTOS','CONTROLE','TPM','ESTRESSE','VIAGENS','ALMOÇOS','FAMÍLIA','RESPOSTA','DURA','MAIORIA','DIETAS','FRACASSA','AGORA','EMAGRECER','CORPO','LUTAR','NOVO','DIMINUI','HORMÔNIOS','SACIEDADE','AUMENTA','FOME','DERRUBA','ENERGIA','SABOTA','SILENCIOSAMENTE','NOME','HORMONAL','HISTÓRIA','MUDAR','PERSPECTIVA','CELEBRANDO','PRIMEIRA','CONQUISTA','HAVIA','PERDIDO','PREOCUPAR','ASSUSTADOR','ENGORDAR','MAIS','PESO','SENSAÇÃO','FRACASSO','ESPELHO','TRANSFORMAÇÃO','VERGONHA','31','12,3','QUILOS','18','97%'
219
+ ]);
220
+
221
+ const veryImportantWords = new Set([
222
+ 'NUNCA MAIS','DEFINITIVAMENTE','LIBERTAR','INSUPORTÁVEL','INSEGURANÇA','TRANSFORMAÇÃO','MUDAR TUDO','A VERDADE','É AGORA','MOMENTO','DECISÃO','A RESPOSTA','ME LIBERTAR','O COMEÇO','TUDO','NADA','MUDA','CULPA','MERECE','SIMPLES','ACESSÍVEL','HOJE','ÚLTIMA','AGORA','NOVA','LIBERDADE','CAMINHO CERTO','NUNCA MAIS VOU VIVER ASSIM','EU ROUBEI','GORDA','TRAVEI','GORDINHA','PAZ','SÓ QUEM SENTE SABE','NÃO AGUENTO MAIS','CHEGA','ACORDEI','EU DECIDI','FOI NAQUELE MOMENTO','SONHO','ALÉM','POSSÍVEL','REAL','ME VI NO ESPELHO','EMOCIONEI','MUDOU','VIREI','NUNCA','MAIS','PERDER','TUDO','ATERRORIZAVA','FRACASSA','REPROGRAMAR','ATERRORIZANTE','ENGANAR','MEMÓRIA','EXCLUSIVA','VIDA','ESTILO','DOERAM','DOR','SOCO','SEMPRE','CERTEZA','ABSOLUTA','CONTROLE','TOTAL','LIBERDADE','SEGURANÇA','CONFIANÇA','PAZ','TRANSFORMAÇÃO','BLINDADA','PROMESSA','CHANCE','ÚNICA','DECIDE','AGORA','HOJE','GARANTIA','GRATUITOS','INVESTIR','URGENTE','INDISPONÍVEL','RESPONSABILIDADE','EXTREMAMENTE','MANTER','SEU','ATENÇÃO','REALIDADE','FRACASSO','JUREI','OUTRA','PESSOA','ENGANAR','DESCOBRI','BÔNUS','GRATUITO','ECONOMIZANDO','PAGANDO','REALIDADES','DIFERENTES','INTELIGENTE','HESITAR','ARRISCAR','CONTADOR','EXPIRA','TEMPO','DEIXE','CLIQUE','ESPERO','LADO','DECISÃO','INTELIGENTE','BEM-VINDA','VIDA','NOVA','COMEÇA'
223
+ ]);
224
+
225
+ function marcarPalavras(texto) {
226
+ return texto.split(/(\s+)/).map(token => {
227
+ const clean = token.normalize('NFD').replace(/\p{Diacritic}/gu, '').replace(/[.,!?;:\"\"\”\“]/g, '').toUpperCase();
228
+ const safe = escapeHtml(token);
229
+ if (veryImportantWords.has(clean)) return `<span class='bold-red'>${safe}</span>`;
230
+ if (importantWords.has(clean)) return `<span class='bold-black'>${safe}</span>`;
231
+ return safe;
232
+ }).join('');
233
+ }
234
+
235
+ function updateSlideDisplay(slideIndex) {
236
+ if (slides.length === 0) return;
237
+ currentSlideIndex = Math.max(0, Math.min(slideIndex, slides.length - 1));
238
+ preview.innerHTML = slides[currentSlideIndex].text;
239
+ slideCounter.textContent = `${currentSlideIndex + 1} / ${slides.length}`;
240
+ }
241
+
242
+ function findCurrentSlideIndex(currentTime) {
243
+ let index = 0;
244
+ for (let i = 0; i < slides.length; i++) {
245
+ if (currentTime >= slides[i].start) {
246
+ index = i;
247
+ } else { break; }
248
+ }
249
+ return index;
250
+ }
251
+
252
+ document.getElementById("jsonFile").addEventListener("change", function () {
253
+ const reader = new FileReader();
254
+ reader.onload = function () {
255
+ try {
256
+ const data = JSON.parse(reader.result);
257
+ slides = [];
258
+ let bloco = [], lastStart = 0;
259
+
260
+ for (let i = 0; i < data.words.length; i++) {
261
+ const w = data.words[i];
262
+ const word = { start: w.start, end: w.end, word: escapeHtml(w.word) }; // sanitize
263
+ if (bloco.length === 0) lastStart = word.start;
264
+ bloco.push(word);
265
+ const isLast = i === data.words.length - 1;
266
+ const nextWord = i < data.words.length - 1 ? data.words[i + 1] : null;
267
+ const longPause = nextWord && (nextWord.start - w.end > 0.7);
268
+ const isEnd = /[.!?]/.test(w.word);
269
+ const isPausaIntencional = longPause || isEnd;
270
+ const criarNovoSlide = isLast || (isPausaIntencional && bloco.length >= 1) || (!isPausaIntencional && bloco.length >= 12);
271
+
272
+ if (criarNovoSlide) {
273
+ if (bloco.length === 1 && !isPausaIntencional && !isLast) continue;
274
+ const palavras = bloco.map(b => b.word);
275
+ if (palavras.length > 0) {
276
+ const first = palavras[0];
277
+ palavras[0] = first.charAt(0).toUpperCase() + first.slice(1);
278
+ }
279
+ slides.push({ text: marcarPalavras(palavras.join(" ").trim()) + '...', start: lastStart });
280
+ bloco = [];
281
+ }
282
+ }
283
+ if (slides.length > 0) {
284
+ updateSlideDisplay(0);
285
+ slideCounter.style.display = 'block';
286
+ transcriptionStatus.classList.add("active");
287
+ }
288
+ updateStartButton();
289
+ } catch (error) {
290
+ alert("Erro ao processar o arquivo JSON. Verifique se o formato está correto.");
291
+ console.error(error);
292
+ }
293
+ }
294
+ reader.readAsText(this.files[0]);
295
+ });
296
+
297
+ document.getElementById("audio").addEventListener("change", function () {
298
+ player.src = URL.createObjectURL(this.files[0]);
299
+ audioReady = true;
300
+ audioStatus.classList.add("active");
301
+ updateStartButton();
302
+ });
303
+
304
+ function updateStartButton() {
305
+ const canStart = audioReady && slides.length > 0;
306
+ startButton.disabled = !canStart;
307
+ if (canStart && !syncStatus.classList.contains("active")) {
308
+ syncStatus.classList.add("active");
309
+ }
310
+ }
311
+
312
+ function toggleFullscreen() {
313
+ const previewEl = document.getElementById("slidePreview");
314
+ previewEl.classList.toggle("fullscreen");
315
+ previewEl.classList.remove("hd");
316
+ if (document.fullscreenElement) {
317
+ document.exitFullscreen();
318
+ } else {
319
+ previewEl.requestFullscreen();
320
+ }
321
+ }
322
+
323
+ function toggle1080p() {
324
+ const previewEl = document.getElementById("slidePreview");
325
+ previewEl.classList.add("fullscreen");
326
+ previewEl.classList.toggle("hd");
327
+ if (!document.fullscreenElement) {
328
+ previewEl.requestFullscreen();
329
+ }
330
+ }
331
+
332
+ function startSync() {
333
+ if (!audioReady || slides.length === 0) {
334
+ alert("Por favor, importe o áudio e a transcrição primeiro.");
335
+ return;
336
+ }
337
+
338
+ // ✅ Se já iniciou, apenas alterna play/pause sem reiniciar
339
+ if (hasStarted) {
340
+ if (player.paused) { player.play(); } else { player.pause(); }
341
+ return;
342
+ }
343
+
344
+ // Primeiro start com contagem regressiva
345
+ const countdown = 5;
346
+ let timeLeft = countdown;
347
+ preview.innerHTML = `Iniciando em ${timeLeft}...`;
348
+ startButton.disabled = true;
349
+
350
+ const timer = setInterval(() => {
351
+ timeLeft--;
352
+ if (timeLeft > 0) {
353
+ preview.innerHTML = `Iniciando em ${timeLeft}...`;
354
+ } else {
355
+ clearInterval(timer);
356
+ player.currentTime = 0;
357
+ player.playbackRate = 1.08; // ajuste fino
358
+ hasStarted = true;
359
+ player.play();
360
+ }
361
+ }, 1000);
362
+ }
363
+
364
+ function syncSlides() {
365
+ if (!isPlaying || slides.length === 0) return;
366
+ const newSlideIndex = findCurrentSlideIndex(player.currentTime);
367
+ if (newSlideIndex !== currentSlideIndex) {
368
+ updateSlideDisplay(newSlideIndex);
369
+ }
370
+ requestAnimationFrame(syncSlides);
371
+ }
372
+
373
+ // Event listeners do player
374
+ player.addEventListener('play', function() {
375
+ if (slides.length > 0) {
376
+ isPlaying = true;
377
+ startButton.innerHTML = "⏸️ Pausar Apresentação";
378
+ startButton.disabled = false;
379
+ syncSlides();
380
+ }
381
+ });
382
+
383
+ player.addEventListener('pause', function() {
384
+ isPlaying = false;
385
+ startButton.innerHTML = "▶️ Continuar Apresentação";
386
+ });
387
+
388
+ player.addEventListener('ended', function() {
389
+ isPlaying = false;
390
+ hasStarted = false; // ✅ permite reiniciar do zero com contagem
391
+ startButton.innerHTML = "▶️ Iniciar Apresentação";
392
+ });
393
+
394
+ // Sincronização quando o usuário navega manualmente no áudio
395
+ player.addEventListener('seeked', function() {
396
+ if (slides.length > 0) {
397
+ const newSlideIndex = findCurrentSlideIndex(player.currentTime);
398
+ updateSlideDisplay(newSlideIndex);
399
+ }
400
+ });
401
+
402
+ // Atalhos de teclado
403
+ document.addEventListener('keydown', function(e) {
404
+ if (e.key === 'Escape') {
405
+ const previewEl = document.getElementById("slidePreview");
406
+ previewEl.classList.remove("fullscreen", "hd");
407
+ if (document.fullscreenElement) { document.exitFullscreen(); }
408
+ } else if (e.key === ' ') {
409
+ // ✅ Espaço agora só pausa/continua (sem reiniciar)
410
+ if (audioReady && slides.length > 0) {
411
+ e.preventDefault();
412
+ if (!hasStarted) { startSync(); } else { if (player.paused) player.play(); else player.pause(); }
413
+ }
414
+ } else if (e.key === 'ArrowLeft' && slides.length > 0) {
415
+ e.preventDefault();
416
+ if (player.currentTime > 1) { player.currentTime = Math.max(0, player.currentTime - 5); }
417
+ } else if (e.key === 'ArrowRight' && slides.length > 0) {
418
+ e.preventDefault();
419
+ player.currentTime = Math.min(player.duration || player.currentTime + 5, player.currentTime + 5);
420
+ }
421
+ }, { capture: true }); // captura garante prioridade sobre atalhos do navegador
422
+
423
+ // Inicialização
424
+ updateStartButton();
425
+ </script>
426
+ </body>
427
  </html>