-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRSVP.html
More file actions
393 lines (329 loc) · 17.6 KB
/
RSVP.html
File metadata and controls
393 lines (329 loc) · 17.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Leitor RSVP Pro</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Inter:wght@300;400;600&display=swap');
body {
background-color: #0a0a0a;
color: #e5e5e5;
font-family: 'Inter', sans-serif;
touch-action: manipulation;
}
.reader-font {
font-family: 'JetBrains Mono', monospace;
}
/* Marcadores de foco centralizados no ORP */
.focus-indicator {
position: absolute;
left: 35%;
transform: translateX(-50%);
width: 2px;
height: 30px;
background: #ef4444;
pointer-events: none;
z-index: 10;
}
.focus-indicator-top { top: 0; border-radius: 0 0 2px 2px; }
.focus-indicator-bottom { bottom: 0; border-radius: 2px 2px 0 0; }
.orp-red {
color: #ef4444;
font-weight: 700;
}
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #1a1a1a;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #333;
border-radius: 10px;
}
#word-display-container {
min-height: 130px;
}
#progress-bar {
transition: width 0.2s ease-out;
}
/* Estilização do controle de velocidade (Slider) */
input[type=range] {
-webkit-appearance: none;
background: transparent;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 4px;
cursor: pointer;
background: #262626;
border-radius: 2px;
}
input[type=range]::-webkit-slider-thumb {
height: 18px;
width: 18px;
border-radius: 50%;
background: #ef4444;
cursor: pointer;
-webkit-appearance: none;
margin-top: -7px;
box-shadow: 0 0 10px rgba(239, 68, 68, 0.4);
}
/* Elemento de renderização sem transição para evitar movimento visível */
#word-render {
position: absolute;
white-space: pre;
will-change: transform;
}
</style>
</head>
<body class="flex flex-col items-center justify-center min-h-screen p-4 overflow-hidden">
<!-- Tela Inicial de Configuração -->
<div id="setup-screen" class="w-full max-w-2xl space-y-6 animate-in fade-in duration-500">
<div class="text-center space-y-2">
<h1 class="text-3xl font-semibold tracking-tight text-white">Leitor RSVP <span class="text-red-500">Pro</span></h1>
<p class="text-gray-400 text-sm">Treine sua leitura dinâmica com foco total.</p>
</div>
<textarea id="text-input"
class="w-full h-80 p-4 bg-neutral-900 border border-neutral-800 rounded-xl focus:ring-2 focus:ring-red-500 focus:border-transparent outline-none transition-all custom-scrollbar text-gray-300 resize-none"
placeholder="Insira o texto aqui...">Speed Reading: O Guia Definitivo de Leitura Dinâmica
Se você conseguisse ler 1000 palavras por minuto, quantos livros leria por ano? Reservando meia hora de leitura por dia, a resposta é 137. Sim, 137 livros em um ano, lendo durante trinta minutos por dia. Quem pratica speed reading (ou leitura dinâmica) alcança esse índice, ou até mais.
Uma pessoa comum lê entre 200 a 400 palavras por minuto. Acima desse número, você já pode ser considerado um leitor ágil, mas existem casos de 3000 palavras por minuto. E é possível chegar lá.
Nos últimos tempos, o conceito de leitura dinâmica entrou em alta. Apesar de existir desde os anos 50, diversos métodos trouxeram uma atualização das técnicas, que não são poucas.
Repare em como você está passando os olhos por esse texto agora. Você quase não percebe, mas lê em blocos de palavras e não de forma contínua. Cada vez que você precisa dar essa parada é chamada de fixação. Para então formar uma linha de raciocínio, os olhos passam de uma fixação para a outra, em um movimento que chamamos de sacadas. Esse é o processo que os praticantes de speed reading querem simplificar e tornar mais rápido.
Os especialistas em speed reading acreditam que o segredo é o treinamento. Basta que você tenha em mente alguns objetivos, como aumentar o número de palavras absorvidas em cada fixação, diminuir o tempo de duração das sacadas e evitar ao máximo a releitura.
A maioria das pessoas fala mentalmente o que está sendo lido. Se esse é o seu caso, é hora de abandonar esse hábito, que funciona como um obstáculo para uma leitura rápida. Isso porque, ao “pronunciar” em sua mente as palavras, antes de compreender os significados delas, você desacelera. Quebre essa barreira, lendo mais rápido e sem pronunciar mentalmente as palavras. Assim, esse novo hábito será reescrito em seu cérebro.
O RSVP (Rapid Serial Visual Presentation) foi o que trouxe o speed reading para o mundo digital. Funciona assim: o aplicativo mostra uma palavra por vez do texto. Dessa forma, não é preciso movimentar os olhos para alcançar as palavras. Mas sua atenção precisa ser esforçada. Esse método parte do princípio de que seus olhos focam em um ponto central da palavra para poder compreendê-la. Além de colocar uma atrás da outra, as palavras são alinhadas para que esse centro (ORP) fique sempre na mesma posição.</textarea>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="bg-neutral-900 p-4 rounded-xl border border-neutral-800 flex items-center justify-between">
<span class="text-sm font-medium text-gray-400">WPM Inicial</span>
<div class="flex items-center space-x-3">
<input type="number" id="wpm-input" value="300" step="50" min="50" max="2000"
class="bg-black border border-neutral-700 rounded-lg px-2 py-1 w-20 text-center text-white focus:ring-1 focus:ring-red-500 outline-none">
</div>
</div>
<button id="btn-start" class="bg-red-600 hover:bg-red-700 text-white font-semibold py-4 rounded-xl transition-all transform active:scale-95 shadow-lg shadow-red-900/20">
Iniciar Treino
</button>
</div>
</div>
<!-- Tela de Leitura -->
<div id="reader-screen" class="hidden w-full max-w-4xl flex flex-col items-center space-y-8">
<!-- Área de Exibição Principal -->
<div class="relative w-full flex flex-col items-center">
<div id="word-display-container" class="reader-font text-4xl md:text-5xl flex items-center w-full bg-neutral-900/50 rounded-2xl border border-neutral-800/50 py-10 relative overflow-hidden">
<!-- Marcadores de Foco Vermelhos -->
<div class="focus-indicator focus-indicator-top"></div>
<div class="focus-indicator focus-indicator-bottom"></div>
<!-- Palavra Ativa -->
<div id="word-render">
<span class="text-neutral-600">Pronto?</span>
</div>
</div>
<!-- Barra de Progresso do Texto -->
<div class="w-full h-1.5 bg-neutral-800 mt-8 rounded-full overflow-hidden">
<div id="progress-bar" class="h-full bg-red-500 w-0"></div>
</div>
</div>
<!-- Ajuste de Velocidade em Tempo Real -->
<div class="w-full max-w-md space-y-3 bg-neutral-900/40 p-5 rounded-2xl border border-neutral-800/50">
<div class="flex justify-between text-xs font-bold uppercase tracking-wider text-neutral-500">
<span>Velocidade de Leitura</span>
<span class="text-red-500"><span id="current-wpm-display">300</span> WPM</span>
</div>
<input type="range" id="wpm-slider" min="50" max="1500" step="10" value="300" class="w-full">
</div>
<!-- Controles de Reprodução -->
<div class="flex items-center space-x-6 bg-neutral-900 px-8 py-4 rounded-full border border-neutral-800 shadow-2xl">
<button id="btn-back" class="text-gray-400 hover:text-white transition-colors p-2" title="Voltar 10 palavras">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
</button>
<button id="btn-play-pause" class="w-16 h-16 bg-white text-black rounded-full flex items-center justify-center hover:bg-gray-200 transition-all transform active:scale-90">
<div id="play-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg>
</div>
<div id="pause-icon" class="hidden">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
</div>
</button>
<button id="btn-stop" class="text-gray-400 hover:text-red-500 transition-colors p-2" title="Sair do modo leitura">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
</button>
</div>
</div>
<!-- Mensagens de Toast -->
<div id="message-box" class="fixed bottom-10 left-1/2 -translate-x-1/2 bg-neutral-800 text-white px-6 py-3 rounded-full shadow-2xl border border-neutral-700 opacity-0 transition-opacity pointer-events-none z-50">
Texto finalizado!
</div>
<script>
// --- Estado Global ---
let state = {
words: [],
currentIndex: 0,
isPlaying: false,
wpm: 300,
timeoutId: null,
orpPercent: 0.35 // Ponto Ótimo de Reconhecimento (35% da largura)
};
// --- Elementos da DOM ---
const setupScreen = document.getElementById('setup-screen');
const readerScreen = document.getElementById('reader-screen');
const textInput = document.getElementById('text-input');
const wordDisplayContainer = document.getElementById('word-display-container');
const wordRender = document.getElementById('word-render');
const progressBar = document.getElementById('progress-bar');
const wpmInput = document.getElementById('wpm-input');
const wpmSlider = document.getElementById('wpm-slider');
const currentWpmDisplay = document.getElementById('current-wpm-display');
const msgBox = document.getElementById('message-box');
const btnStart = document.getElementById('btn-start');
const btnPlayPause = document.getElementById('btn-play-pause');
const btnStop = document.getElementById('btn-stop');
const btnBack = document.getElementById('btn-back');
const playIcon = document.getElementById('play-icon');
const pauseIcon = document.getElementById('pause-icon');
/**
* Calcula o índice da letra ORP baseado no tamanho da palavra.
*/
function getOrpIndex(word) {
const len = word.length;
if (len <= 1) return 0;
if (len <= 5) return 1;
if (len <= 9) return 2;
if (len <= 13) return 3;
return 4;
}
/**
* Renderiza a palavra alinhando o ORP com os marcadores vermelhos.
*/
function renderWord(word) {
if (!word) return;
const orpIdx = getOrpIndex(word);
const prefix = word.substring(0, orpIdx);
const pivot = word[orpIdx];
const suffix = word.substring(orpIdx + 1);
wordRender.innerHTML = '';
const preSpan = document.createElement('span');
preSpan.textContent = prefix;
preSpan.className = 'text-white';
const pivotSpan = document.createElement('span');
pivotSpan.textContent = pivot;
pivotSpan.className = 'orp-red';
const sufSpan = document.createElement('span');
sufSpan.textContent = suffix;
sufSpan.className = 'text-white';
wordRender.appendChild(preSpan);
wordRender.appendChild(pivotSpan);
wordRender.appendChild(sufSpan);
// Alinhamento matemático instantâneo
const containerWidth = wordDisplayContainer.offsetWidth;
const targetX = containerWidth * state.orpPercent;
const pivotCenterOffset = preSpan.offsetWidth + (pivotSpan.offsetWidth / 2);
const shift = targetX - pivotCenterOffset;
wordRender.style.transform = `translateX(${shift}px)`;
}
// --- Funções de Controle ---
function showToast(text) {
msgBox.textContent = text;
msgBox.style.opacity = '1';
setTimeout(() => msgBox.style.opacity = '0', 3000);
}
function updateProgress() {
if (state.words.length === 0) return;
const percent = (state.currentIndex / state.words.length) * 100;
progressBar.style.width = `${percent}%`;
}
function nextWord() {
if (state.currentIndex >= state.words.length) {
stopReading();
showToast("Treino finalizado!");
return;
}
const word = state.words[state.currentIndex];
renderWord(word);
updateProgress();
let delay = 60000 / state.wpm;
// Atrasos inteligentes para pontuação e clareza
if (word.endsWith('.') || word.endsWith('!') || word.endsWith('?')) {
delay *= 2.2;
} else if (word.endsWith(',') || word.endsWith(';') || word.endsWith(':')) {
delay *= 1.6;
} else if (word.length > 10) {
delay *= 1.3;
}
state.currentIndex++;
state.timeoutId = setTimeout(nextWord, delay);
}
function startReading() {
const rawText = textInput.value;
if (!rawText.trim()) return;
state.words = rawText.trim().split(/[\s\n]+/).filter(w => w.length > 0);
state.currentIndex = 0;
const initialWpm = parseInt(wpmInput.value) || 300;
updateWpm(initialWpm);
setupScreen.classList.add('hidden');
readerScreen.classList.remove('hidden');
// Aguarda renderização do layout para cálculo de offset correto
setTimeout(() => {
togglePlay(true);
}, 100);
}
function updateWpm(newWpm) {
state.wpm = parseInt(newWpm);
wpmSlider.value = state.wpm;
wpmInput.value = state.wpm;
currentWpmDisplay.textContent = state.wpm;
}
function togglePlay(forcePlay = null) {
const targetState = forcePlay !== null ? forcePlay : !state.isPlaying;
if (targetState) {
if (state.isPlaying) return;
state.isPlaying = true;
playIcon.classList.add('hidden');
pauseIcon.classList.remove('hidden');
nextWord();
} else {
state.isPlaying = false;
playIcon.classList.remove('hidden');
pauseIcon.classList.add('hidden');
clearTimeout(state.timeoutId);
}
}
function stopReading() {
togglePlay(false);
state.currentIndex = 0;
setupScreen.classList.remove('hidden');
readerScreen.classList.add('hidden');
wordRender.innerHTML = '<span class="text-neutral-600">Pronto?</span>';
progressBar.style.width = '0%';
}
function goBack() {
state.currentIndex = Math.max(0, state.currentIndex - 10);
updateProgress();
if (!state.isPlaying && state.words.length > 0) {
renderWord(state.words[state.currentIndex]);
}
}
// --- Event Listeners ---
btnStart.addEventListener('click', startReading);
btnPlayPause.addEventListener('click', () => togglePlay());
btnStop.addEventListener('click', stopReading);
btnBack.addEventListener('click', goBack);
wpmSlider.addEventListener('input', (e) => updateWpm(e.target.value));
window.addEventListener('keydown', (e) => {
if (readerScreen.classList.contains('hidden')) return;
if (e.code === 'Space') { e.preventDefault(); togglePlay(); }
else if (e.code === 'Escape') { stopReading(); }
else if (e.code === 'ArrowLeft') { goBack(); }
});
window.addEventListener('resize', () => {
if (state.words.length > 0) {
const idx = Math.max(0, state.currentIndex - 1);
renderWord(state.words[idx]);
}
});
</script>
</body>
</html>