Treino de Digitação — Velocidade e Precisão

Treino interativo

Laboratório de Digitação — Notícias e Atualidades

Escolha o tema, o nível e o tempo. Depois clique em Iniciar treino. O cronômetro começa na hora e o campo de digitação abre automaticamente.

Como usar 1. Escolha tema e nível. 2. Defina o tempo. 3. Clique em Iniciar. 4. Digite, apague e corrija normalmente.
Texto preparado: Atualidades • Iniciante
Pronto para começar
Tempo 00:00 decorrido
Velocidade 0 PPM palavras por minuto
Precisão 100% acertos por tentativa
Erros 0 teclas incorretas
Progresso 0%

Posição dos dedos

As cores mostram qual dedo deve alcançar cada grupo de teclas.

Use agora: Polegar
Mão esquerda A • S • D • F
Mind.
Anel.
Médio
Indic.
Pol.
Mão direita J • K • L • Ç
Pol.
Indic.
Médio
Anel.
Mind.
1
2
3
4
5
6
7
8
9
0
Tab
Q
W
E
R
T
Y
U
I
O
P
Caps
A
S
D
F
G
H
J
K
L
Ç
Enter
Shift
Z
X
C
V
B
N
M
,
.
; :
Shift
ESPAÇO
Mindinho E. Anelar E. Médio E. Indicador E. Indicador D. Médio D. Anelar D. Mindinho D. Polegar

Treino de digitação

Você pode errar, apagar, mover o cursor e corrigir normalmente.

Tempo restante 03:00 decorrido 00:00
Próxima tecla
Clique em “Iniciar treino” para começar.
⌫ Backspace apaga à esquerda Delete apaga à direita ← → movem o cursor Clique onde deseja corrigir

Treino concluído

Tempo 00:00
PPM 0
Precisão 100%
Erros 0
Chegou até 0%
Digitado 0/0

Palavras em que houve erro

Mesmo que você tenha corrigido depois, elas ficam aqui para você saber o que precisa treinar.

Nenhum erro registrado.

(function(){ function initTypingLab(){ const root = document.getElementById('typingLab'); if( !root || root.dataset.ready === '1' ){ return; } root.dataset.ready = '1'; const $ = id => document.getElementById(id); const qa = selector => Array.from( root.querySelectorAll(selector) ); const el = { theme: $('typingTheme'), level: $('typingLevel'), duration: $('typingDuration'), start: $('typingStart'), pause: $('typingPause'), finish: $('typingFinish'), finishInline: $('typingFinishInline'), newText: $('typingNew'), restart: $('typingRestart'), selection: $('typingSelection'), status: $('typingStatus'), elapsedTop: $('typingElapsedTop'), wpm: $('typingWpm'), accuracy: $('typingAccuracy'), errors: $('typingErrors'), progressText: $('typingProgressText'), progressBar: $('typingProgressBar'), source: $('typingSource'), input: $('typingInput'), remaining: $('typingRemaining'), elapsedSmall: $('typingElapsedSmall'), nextChar: $('typingNextChar'), nextHelp: $('typingNextHelp'), fingerName: $('typingFingerName'), fingerDot: $('typingFingerDot'), panel: $('typingTrainingPanel'), writeSide: $('typingWriteSide'), result: $('typingResult'), resultTitle: $('typingResultTitle'), resultMessage: $('typingResultMessage'), resultTime: $('typingResultTime'), resultWpm: $('typingResultWpm'), resultAccuracy: $('typingResultAccuracy'), resultErrors: $('typingResultErrors'), resultProgress: $('typingResultProgress'), resultTyped: $('typingResultTyped'), errorWords: $('typingErrorWords'), again: $('typingAgain'), another: $('typingAnother') }; const required = Object.values(el) .filter(Boolean); if( required.length !== Object.keys(el).length ){ console.error( 'Laboratório de Digitação: algum elemento obrigatório não foi encontrado.' ); root.dataset.ready = '0'; return; } const texts = { atualidades:{ iniciante: 'A leitura de notícias ajuda a compreender melhor o que acontece na cidade, no país e no mundo. Todos os dias surgem informações sobre educação, saúde, transporte, tecnologia e trabalho. Ao praticar digitação com textos informativos, o aluno melhora a atenção, amplia o vocabulário e aprende a usar o teclado com mais segurança. O objetivo não é correr no início. Primeiro, é importante acertar as letras, manter uma postura confortável e criar um ritmo constante. Com alguns minutos de treino por dia, a velocidade tende a aumentar naturalmente.', intermediario: 'A circulação de informações ficou muito mais rápida com o uso da internet e dos dispositivos móveis. Hoje, uma notícia pode alcançar milhares de pessoas em poucos minutos, o que aumenta a importância da leitura atenta e da verificação das fontes. Para quem está aprendendo informática, trabalhar com textos de atualidades também é uma oportunidade de desenvolver habilidades práticas. A digitação exige coordenação, percepção visual, memória muscular e atenção aos detalhes. Quanto mais o aluno treina sem olhar para o teclado, mais natural se torna o movimento dos dedos e maior é a confiança para escrever documentos, pesquisar conteúdos e preencher formulários digitais.', avancado: 'A transformação digital alterou profundamente a maneira como as pessoas produzem, recebem e compartilham informação. Portais de notícias, redes sociais, aplicativos de mensagens e plataformas de vídeo disputam a atenção do público em tempo real. Nesse ambiente, ler com atenção passou a ser tão importante quanto saber localizar conteúdos. Para o estudante que está desenvolvendo competências digitais, a prática de digitação oferece ganhos que vão além da velocidade. Ela melhora a coordenação entre visão e movimento, reduz o esforço para escrever textos longos e favorece a autonomia em tarefas acadêmicas e profissionais. Um bom treino deve combinar precisão, constância e postura adequada, permitindo que a memória muscular seja construída de maneira gradual e segura.' }, tecnologia:{ iniciante: 'A tecnologia está presente em muitas atividades do dia a dia. Usamos computadores e celulares para estudar, trabalhar, conversar e buscar informações. Aprender a digitar ajuda a utilizar essas ferramentas com mais facilidade. No começo, é normal procurar algumas letras no teclado. Com a prática, os dedos começam a memorizar os caminhos. O ideal é manter as mãos na posição correta, olhar mais para a tela e tentar pressionar cada tecla com o dedo indicado. Assim, a digitação fica mais confortável e segura.', intermediario: 'Ferramentas digitais transformaram a forma como as pessoas estudam, trabalham e se comunicam. Editores de texto, navegadores, serviços em nuvem e plataformas de aprendizagem estão presentes em diferentes atividades. Quem domina o teclado consegue realizar essas tarefas com mais fluidez e concentração, porque não precisa interromper o raciocínio para procurar cada letra. A digitação eficiente é construída por repetição. O aluno deve procurar usar o dedo correto para cada tecla, manter os braços relaxados e evitar movimentos exagerados das mãos. Com o tempo, a memória muscular assume parte do trabalho e a escrita se torna mais rápida.', avancado: 'Novas plataformas de inteligência artificial, automação e colaboração online modificam continuamente a rotina de estudantes e profissionais. Apesar dessas mudanças, o teclado permanece como uma das principais formas de entrada de informações em computadores. Dominar a digitação permite escrever comandos, produzir relatórios, responder mensagens e registrar dados de maneira mais ágil. Para alcançar esse domínio, é necessário praticar com textos progressivamente maiores, observar quais teclas causam mais dificuldade e corrigir a técnica antes de aumentar a velocidade. A evolução real aparece quando o aluno consegue manter precisão elevada mesmo em exercícios mais longos.' }, educacao:{ iniciante: 'Aprender é um processo que acontece aos poucos. Cada exercício ajuda o estudante a desenvolver uma habilidade nova. Na digitação, o princípio é o mesmo. Primeiro, é importante conhecer a posição das mãos. Depois, o aluno aprende a alcançar as teclas sem mover os braços em excesso. A prática frequente ajuda a escrever com mais segurança. Errar faz parte do treino, mas cada erro deve servir para ajustar o movimento e melhorar a próxima tentativa.', intermediario: 'O desenvolvimento de habilidades digitais faz parte da formação de estudantes de diferentes idades. Saber usar o teclado com autonomia ajuda na produção de trabalhos, pesquisas, formulários e comunicações online. A digitação também fortalece a concentração, porque exige atenção simultânea ao texto e ao movimento das mãos. Para evoluir, o aluno deve evitar a pressa, manter postura confortável e praticar regularmente. A velocidade tende a crescer quando os dedos deixam de depender da visão e passam a responder pela memória muscular.', avancado: 'A educação digital não se resume ao acesso a computadores e aplicativos. Ela também envolve o desenvolvimento de competências práticas que permitem utilizar essas ferramentas de maneira independente. A digitação é uma dessas competências, pois influencia diretamente a produção de textos, a comunicação escrita e o preenchimento de sistemas. Quando o aluno domina o teclado, consegue concentrar mais atenção nas ideias e menos na localização das letras. O treinamento progressivo deve combinar textos variados, precisão elevada e revisão dos movimentos que causam mais erros. Dessa forma, a evolução ocorre de maneira consciente e sustentável.' }, esportes:{ iniciante: 'O esporte ensina que a evolução depende de prática e disciplina. Na digitação, acontece algo parecido. Quanto mais o aluno repete os movimentos corretos, mais fácil fica encontrar as teclas. O objetivo inicial não é ser o mais rápido, mas criar um bom hábito. Mantenha as mãos na posição correta, use os dedos indicados pelas cores e tente escrever com ritmo constante. A velocidade aparece depois, como resultado do treino.', intermediario: 'O treinamento esportivo é baseado em repetição, técnica e acompanhamento de desempenho. Esses mesmos princípios podem ser aplicados à digitação. O estudante melhora quando pratica movimentos corretos, observa seus erros e tenta superar os próprios resultados de forma gradual. A velocidade, medida em palavras por minuto, é apenas um dos indicadores. A precisão também é fundamental, pois um texto rápido com muitos erros exige correções e perde eficiência. O melhor objetivo é aumentar a velocidade mantendo uma taxa alta de acertos.', avancado: 'A busca por desempenho no esporte costuma envolver metas, indicadores e repetição técnica. Um programa de digitação pode seguir a mesma lógica: registrar tempo, velocidade, precisão e número de erros para mostrar a evolução ao longo das sessões. Entretanto, os números só fazem sentido quando a técnica é preservada. Aumentar palavras por minuto usando apenas alguns dedos pode criar um limite de evolução e aumentar o esforço das mãos. Distribuir corretamente as teclas entre os dedos permite movimentos menores, ritmo mais estável e maior eficiência em textos extensos.' }, trabalho:{ iniciante: 'Muitas profissões utilizam computadores para escrever mensagens, preencher sistemas e organizar informações. Saber digitar facilita essas tarefas e pode trazer mais segurança para quem está começando no mundo do trabalho. A prática deve ser tranquila. Primeiro, procure acertar as letras. Depois, tente escrever sem olhar tanto para o teclado. Com o tempo, os movimentos ficam mais rápidos e naturais.', intermediario: 'A rotina profissional de muitas áreas exige contato constante com sistemas digitais. Atendimento, cadastro, produção de documentos, envio de mensagens e organização de dados são exemplos de tarefas em que a digitação aparece. Quando o trabalhador domina o teclado, consegue registrar informações com menos interrupções e concentrar mais atenção no conteúdo. A precisão é especialmente importante em nomes, números e dados que não podem ser digitados incorretamente. Por isso, um bom treinamento valoriza tanto velocidade quanto controle.', avancado: 'No mercado de trabalho, produtividade não significa apenas fazer uma tarefa rapidamente, mas executá-la com qualidade e menor necessidade de correção. A digitação exemplifica bem essa relação. Um profissional que escreve com velocidade moderada e alta precisão pode ser mais eficiente do que alguém muito rápido que precisa revisar constantemente o que produziu. O treinamento técnico ajuda a reduzir movimentos desnecessários, distribuir o esforço entre os dedos e manter ritmo estável. Essa base facilita o uso de sistemas, planilhas, documentos e ferramentas de comunicação em diferentes áreas.' } }; const fingerNames = { lp:'Mindinho esquerdo', lr:'Anelar esquerdo', lm:'Médio esquerdo', li:'Indicador esquerdo', ri:'Indicador direito', rm:'Médio direito', rr:'Anelar direito', rp:'Mindinho direito', th:'Polegar' }; const fingerColors = { lp:'#ef6b73', lr:'#f4b942', lm:'#48b788', li:'#4f8ef7', ri:'#7c67e8', rm:'#27a8bd', rr:'#e88b3c', rp:'#d45ca7', th:'#68778f' }; const keyFinger = { q:'lp', a:'lp', z:'lp', '1':'lp', w:'lr', s:'lr', x:'lr', '2':'lr', e:'lm', d:'lm', c:'lm', '3':'lm', r:'li', f:'li', v:'li', t:'li', g:'li', b:'li', '4':'li', '5':'li', y:'ri', h:'ri', n:'ri', u:'ri', j:'ri', m:'ri', '6':'ri', '7':'ri', i:'rm', k:'rm', ',':'rm', '8':'rm', o:'rr', l:'rr', '.':'rr', '9':'rr', p:'rp', 'ç':'rp', ';':'rp', '0':'rp', ' ':'th' }; let state = { text:'', running:false, paused:false, finished:false, startMark:0, accumulatedMs:0, timer:null, errors:0, attempts:0, maxReached:0, mistakeWords: new Set(), reason:'' }; function textForSelection(){ return texts [el.theme.value] [el.level.value]; } function selectedLabel(select){ return select .options[ select.selectedIndex ] .text; } function now(){ return performance.now(); } function limitSeconds(){ return Math.max( 0, Number( el.duration.value ) || 0 ); } function elapsedSeconds(){ const ms = state.accumulatedMs + ( state.running ? now() - state.startMark : 0 ); return Math.max( 0, ms / 1000 ); } function formatTime(seconds){ const total = Math.max( 0, Math.floor(seconds) ); const minutes = Math.floor( total / 60 ) .toString() .padStart( 2, '0' ); const secondsLeft = ( total % 60 ) .toString() .padStart( 2, '0' ); return ( minutes + ':' + secondsLeft ); } function normalizeKey(ch){ if(!ch){ return ''; } if(ch === ' '){ return ' '; } const c = ch.toLocaleLowerCase( 'pt-BR' ); const accents = { á:'a', à:'a', â:'a', ã:'a', ä:'a', é:'e', è:'e', ê:'e', ë:'e', í:'i', ì:'i', î:'i', ï:'i', ó:'o', ò:'o', ô:'o', õ:'o', ö:'o', ú:'u', ù:'u', û:'u', ü:'u' }; if(accents[c]){ return accents[c]; } if(c === 'ç'){ return 'ç'; } if( /[a-z0-9]/.test(c) ){ return c; } if( c === ',' || c === '.' ){ return c; } if( c === ';' || c === ':' || c === '?' || c === '!' ){ return ';'; } return ''; } function fingerForChar(ch){ return ( keyFinger[ normalizeKey(ch) ] || 'th' ); } function escapeHtml(value){ return String(value) .replace( /[&<>'"]/g, character => ({ '&':'&', '<':'<', '>':'>', "'":''', '"':'"' })[character] ); } function currentCaret(){ if( typeof el.input.selectionStart === 'number' ){ return Math.max( 0, Math.min( state.text.length, el.input.selectionStart ) ); } return Math.min( state.text.length, el.input.value.length ); } function wordAt(position){ if(!state.text){ return ''; } let pos = Math.max( 0, Math.min( state.text.length - 1, position ) ); if( /\s/.test( state.text[pos] || '' ) ){ pos = Math.max( 0, pos - 1 ); } let start = pos; let end = pos; while( start > 0 && !/\s/.test( state.text[ start - 1 ] ) ){ start--; } while( end < state.text.length && !/\s/.test( state.text[end] ) ){ end++; } return state.text .slice( start, end ) .replace( /^[“"'([{]+|[.,!?;:)}\]”"']+$/g, '' ) .trim(); } function metrics(){ const seconds = elapsedSeconds(); const minutes = Math.max( seconds / 60, 1 / 60 ); const correctAttempts = Math.max( 0, state.attempts - state.errors ); const wpm = Math.max( 0, Math.round( ( correctAttempts / 5 ) / minutes ) ); const accuracy = state.attempts ? Math.max( 0, Math.round( ( correctAttempts / state.attempts ) * 100 ) ) : 100; const progress = state.text.length ? Math.min( 100, Math.round( ( state.maxReached / state.text.length ) * 100 ) ) : 0; return { secs:seconds, wpm:wpm, accuracy:accuracy, progress:progress }; } function renderSource(){ const typed = el.input.value; const caret = currentCaret(); const fragment = document .createDocumentFragment(); for( let i = 0; i < state.text.length; i++ ){ const span = document .createElement( 'span' ); span.textContent = state.text[i]; if( i < typed.length ){ span.className = typed[i] === state.text[i] ? 'ok' : 'wrong'; }else{ span.className = 'rest'; } if( i === caret ){ span .classList .add( state.finished ? 'stopped' : 'current' ); } fragment .appendChild(span); } el.source .replaceChildren( fragment ); } function updateGuide(){ qa( '.typing-key.is-next' ) .forEach( element => element .classList .remove( 'is-next' ) ); qa( '.typing-finger.is-active' ) .forEach( element => element .classList .remove( 'is-active' ) ); const position = currentCaret(); const character = state.text[ position ] || ''; const key = normalizeKey( character ); const finger = fingerForChar( character ); if(key){ const keyElement = qa( '.typing-key' ) .find( item => item.dataset.key === key ); if(keyElement){ keyElement .classList .add( 'is-next' ); } } qa( '.typing-finger[data-hand-finger="' + finger + '"]' ) .forEach( item => item .classList .add( 'is-active' ) ); el.nextChar.textContent = character === ' ' ? '␣' : ( character ? character .toLocaleUpperCase( 'pt-BR' ) : '—' ); el.fingerName.textContent = fingerNames[ finger ]; el.fingerDot.style.background = fingerColors[ finger ]; if( state.finished ){ el.nextHelp.textContent = character ? 'Você parou aqui. A próxima tecla seria ' + ( character === ' ' ? 'ESPAÇO' : '“' + character + '”' ) + '.' : 'Você chegou ao final do texto.'; }else if( state.paused ){ el.nextHelp.textContent = 'Treino pausado. Clique em Continuar para retomar.'; }else if( !state.running ){ el.nextHelp.textContent = 'Clique em “Iniciar treino” para começar.'; }else{ el.nextHelp.textContent = character === ' ' ? 'Pressione a barra de espaço.' : 'Digite “' + character + '” usando ' + fingerNames[ finger ] .toLowerCase() + '.'; } } function updateStats(){ const data = metrics(); const limit = limitSeconds(); const remaining = limit ? Math.max( 0, limit - data.secs ) : 0; el.elapsedTop.textContent = formatTime( data.secs ); el.wpm.textContent = data.wpm; el.accuracy.textContent = data.accuracy + '%'; el.errors.textContent = state.errors; el.progressText.textContent = data.progress + '%'; el.progressBar.style.width = data.progress + '%'; el.elapsedSmall.textContent = 'decorrido ' + formatTime( data.secs ); el.remaining.textContent = limit ? formatTime( remaining ) : '∞'; } function timerTick(){ if( !state.running ){ return; } updateStats(); const limit = limitSeconds(); if( limit > 0 && elapsedSeconds() >= limit ){ finish( 'time' ); } } function startTimer(){ stopTimer(); state.startMark = now(); state.timer = setInterval( timerTick, 200 ); timerTick(); } function stopTimer(){ if( state.timer ){ clearInterval( state.timer ); state.timer = null; } } function scrollToInput(){ const behavior = window .matchMedia( '(prefers-reduced-motion: reduce)' ) .matches ? 'auto' : 'smooth'; el.writeSide .classList .add( 'is-guided' ); el.writeSide .scrollIntoView({ behavior:behavior, block:'center' }); setTimeout( function(){ if( !state.finished && !state.paused ){ el.input.focus({ preventScroll:true }); } el.writeSide .classList .remove( 'is-guided' ); }, behavior === 'smooth' ? 450 : 20 ); } function prepare(newText){ stopTimer(); state.running = false; state.paused = false; state.finished = false; state.startMark = 0; state.accumulatedMs = 0; state.errors = 0; state.attempts = 0; state.maxReached = 0; state.mistakeWords = new Set(); state.reason = ''; if( newText || !state.text ){ state.text = textForSelection(); } el.input.value = ''; el.input.disabled = true; el.input.maxLength = state.text.length; el.source .classList .add( 'typing-source-locked' ); el.status.className = 'typing-status'; el.status.textContent = 'Pronto para começar'; el.selection.innerHTML = 'Texto preparado: ' + selectedLabel( el.theme ) + ' • ' + selectedLabel( el.level ) + ' • ' + state.text .split(/\s+/) .length + ' palavras'; el.result .classList .remove( 'is-visible' ); el.start.disabled = false; el.pause.disabled = true; el.finish.disabled = true; el.finishInline.disabled = true; el.restart.disabled = true; el.newText.disabled = false; el.theme.disabled = false; el.level.disabled = false; el.duration.disabled = false; el.pause.textContent = '⏸ Pausar'; renderSource(); updateStats(); updateGuide(); } function start(){ prepare( false ); state.running = true; state.paused = false; el.input.disabled = false; el.source .classList .remove( 'typing-source-locked' ); el.status.className = 'typing-status is-running'; el.status.textContent = 'Treino em andamento'; el.start.disabled = true; el.pause.disabled = false; el.finish.disabled = false; el.finishInline.disabled = false; el.restart.disabled = false; el.newText.disabled = true; el.theme.disabled = true; el.level.disabled = true; el.duration.disabled = true; startTimer(); updateGuide(); scrollToInput(); } function pause(){ if( !state.running || state.finished ){ return; } state.accumulatedMs += ( now() - state.startMark ); state.running = false; state.paused = true; stopTimer(); el.input.disabled = true; el.pause.textContent = '▶ Continuar'; el.status.className = 'typing-status'; el.status.textContent = 'Treino pausado'; updateStats(); updateGuide(); } function resume(){ if( !state.paused || state.finished ){ return; } state.running = true; state.paused = false; el.input.disabled = false; el.pause.textContent = '⏸ Pausar'; el.status.className = 'typing-status is-running'; el.status.textContent = 'Treino em andamento'; startTimer(); updateGuide(); scrollToInput(); } function renderErrorWords(){ if( !state.mistakeWords.size ){ el.errorWords.innerHTML = 'Nenhum erro registrado. Excelente precisão.'; return; } el.errorWords.innerHTML = Array.from( state.mistakeWords ) .slice( 0, 20 ) .map( word => '' + escapeHtml(word) + '' ) .join(''); } function finish(reason){ if( state.finished ){ return; } if( state.running ){ state.accumulatedMs += ( now() - state.startMark ); } const limit = limitSeconds(); if( reason === 'time' && limit > 0 ){ state.accumulatedMs = limit * 1000; } state.running = false; state.paused = false; state.finished = true; state.reason = reason; stopTimer(); el.input.disabled = true; el.pause.disabled = true; el.finish.disabled = true; el.finishInline.disabled = true; el.pause.textContent = '⏸ Pausar'; el.newText.disabled = false; el.theme.disabled = false; el.level.disabled = false; el.duration.disabled = false; updateStats(); renderSource(); updateGuide(); const data = metrics(); const completed = el.input.value === state.text; el.status.className = 'typing-status'; el.status.textContent = completed ? 'Treino concluído' : ( reason === 'time' ? 'Tempo encerrado' : 'Treino encerrado' ); el.resultTitle.textContent = completed ? 'Treino concluído' : ( reason === 'time' ? 'Tempo encerrado' : 'Treino encerrado' ); el.resultTime.textContent = formatTime( data.secs ); el.resultWpm.textContent = data.wpm; el.resultAccuracy.textContent = data.accuracy + '%'; el.resultErrors.textContent = state.errors; el.resultProgress.textContent = data.progress + '%'; el.resultTyped.textContent = state.maxReached + '/' + state.text.length; renderErrorWords(); if( completed ){ el.resultMessage.textContent = 'Você concluiu todo o texto. Confira sua velocidade, precisão e as palavras que exigiram correção.'; }else if( reason === 'time' ){ el.resultMessage.textContent = 'O tempo terminou. Você chegou a ' + data.progress + '% do texto. O ponto em que parou continua destacado acima.'; }else{ el.resultMessage.textContent = 'Você finalizou neste ponto e chegou a ' + data.progress + '% do texto. O local onde parou continua destacado acima.'; } el.result .classList .add( 'is-visible' ); el.result .scrollIntoView({ behavior: window .matchMedia( '(prefers-reduced-motion: reduce)' ) .matches ? 'auto' : 'smooth', block:'nearest' }); } function flashKey(key){ const normalized = normalizeKey( key ); const keyElement = qa( '.typing-key' ) .find( item => item.dataset.key === normalized ); if( keyElement ){ keyElement .classList .remove( 'is-wrong' ); void keyElement.offsetWidth; keyElement .classList .add( 'is-wrong' ); setTimeout( function(){ keyElement .classList .remove( 'is-wrong' ); }, 500 ); } el.input .classList .add( 'is-error' ); setTimeout( function(){ el.input .classList .remove( 'is-error' ); }, 220 ); } function setPressed( key, down ){ let visual = key; if( key.length === 1 ){ visual = normalizeKey( key ); } if( key === 'Shift' ){ visual = 'Shift'; } const keyElement = qa( '.typing-key' ) .find( item => item.dataset.key === visual ); if( keyElement ){ keyElement .classList .toggle( 'is-pressed', down ); } } el.input .addEventListener( 'keydown', function(event){ if( !state.running ){ return; } const limit = limitSeconds(); if( limit > 0 && elapsedSeconds() >= limit ){ event.preventDefault(); finish( 'time' ); return; } setPressed( event.key, true ); if( event.ctrlKey || event.metaKey || event.altKey ){ return; } if( event.key.length !== 1 ){ return; } const position = currentCaret(); const expected = state.text[ position ]; state.attempts++; if( expected !== undefined && event.key !== expected ){ state.errors++; const word = wordAt( position ); if( word ){ state .mistakeWords .add( word ); } flashKey( event.key ); } } ); el.input .addEventListener( 'keyup', function(event){ setPressed( event.key, false ); updateGuide(); } ); el.input .addEventListener( 'input', function(){ if( !state.running ){ return; } if( el.input.value.length > state.text.length ){ el.input.value = el.input.value .slice( 0, state.text.length ); } state.maxReached = Math.max( state.maxReached, el.input.value.length ); renderSource(); updateStats(); updateGuide(); if( el.input.value === state.text ){ finish( 'complete' ); } } ); el.input .addEventListener( 'click', function(){ renderSource(); updateGuide(); } ); el.input .addEventListener( 'select', function(){ renderSource(); updateGuide(); } ); el.input .addEventListener( 'paste', function(event){ event.preventDefault(); } ); el.input .addEventListener( 'drop', function(event){ event.preventDefault(); } ); el.start .addEventListener( 'click', start ); el.pause .addEventListener( 'click', function(){ if( state.paused ){ resume(); }else{ pause(); } } ); el.finish .addEventListener( 'click', function(){ finish( 'manual' ); } ); el.finishInline .addEventListener( 'click', function(){ finish( 'manual' ); } ); el.newText .addEventListener( 'click', function(){ prepare( true ); } ); el.restart .addEventListener( 'click', start ); el.again .addEventListener( 'click', start ); el.another .addEventListener( 'click', function(){ prepare( true ); start(); } ); el.theme .addEventListener( 'change', function(){ prepare( true ); } ); el.level .addEventListener( 'change', function(){ prepare( true ); } ); el.duration .addEventListener( 'change', function(){ updateStats(); updateGuide(); } ); window .addEventListener( 'focus', timerTick ); document .addEventListener( 'visibilitychange', function(){ if( !document.hidden ){ timerTick(); } } ); prepare( true ); } if( document.readyState === 'loading' ){ document .addEventListener( 'DOMContentLoaded', initTypingLab, { once:true } ); }else{ initTypingLab(); } })();