Typing Club EASY
⌨️ Typing Club
EASY
⌨️

Typing Club

EASY
`; } function showRegister(){ document.getElementById("authBox").innerHTML=`

Create account

Already have an account?

`; } function message(t){ var x=document.getElementById("authMessage"); if(x)x.innerHTML= '

'+esc(t)+'

'; } function register(){ var n=document.getElementById("registerName").value.trim(); var p=document.getElementById("registerPassword").value; if(n.length<2){ message("Name is too short."); return; } if(p.length<4){ message("Password must contain at least 4 characters."); return; } if(db.users.some(function(u){ return u.name.toLowerCase()===n.toLowerCase(); })){ message("This name is already taken."); return; } var u={ id:uid(), name:n, password:p, device:null, groups:[], xp:0, wpm:0, accuracy:100, completed:[], creator:false }; db.users.push(u); save(); window.currentSession={id:u.id}; localStorage.setItem( SESSIONKEY, JSON.stringify(window.currentSession) ); document.getElementById("loginScreen").classList.add("hidden"); document.getElementById("deviceScreen").classList.remove("hidden"); } function login(){ var n=document.getElementById("loginName").value.trim(); var p=document.getElementById("loginPassword").value; var u=db.users.find(function(x){ return x.name.toLowerCase()===n.toLowerCase(); }); if(!u){ message("Account not found."); return; } if(u.password!==p){ message("Wrong password."); return; } window.currentSession={id:u.id}; localStorage.setItem( SESSIONKEY, JSON.stringify(window.currentSession) ); document.getElementById("loginScreen").classList.add("hidden"); if(u.device){ openApp(); }else{ document.getElementById("deviceScreen").classList.remove("hidden"); } } /* ===================================================== DEVICE ===================================================== */ function chooseDevice(d){ var u=currentUser(); if(!u)return; u.device=d; save(); document.getElementById("deviceScreen").classList.add("hidden"); openApp(); } function setDevice(d){ var u=currentUser(); u.device=d; save(); openApp(); } /* ===================================================== APP ===================================================== */ function openApp(){ var u=currentUser(); if(!u)return; document.getElementById("app").classList.remove("hidden"); document.getElementById("userTop").textContent="👤 "+u.name; updateCreatorButton(); renderLessons(); loadLesson(); renderGroups(); page("training"); } function page(p){ document.querySelectorAll(".page").forEach(function(x){ x.classList.remove("active"); }); var target=document.getElementById(p+"Page"); if(!target)return; target.classList.add("active"); if(p==="training"){ renderLessons(); loadLesson(); } if(p==="groups"){ renderGroups(); } if(p==="leaderboard"){ renderLeaderboard(); } if(p==="profile"){ renderProfile(); } if(p==="creator"){ var u=currentUser(); if(!u || !u.creator){ alert("🔒 Join Creators first."); page("groups"); return; } renderCreator(); } } /* ===================================================== CREATOR BUTTON ===================================================== */ function updateCreatorButton(){ var btn=document.getElementById("creatorBtn"); var u=currentUser(); if(u && u.creator){ btn.classList.remove("hidden"); }else{ btn.classList.add("hidden"); } } /* ===================================================== TRAINING ===================================================== */ var currentLesson=0; var position=0; var correct=0; var wrong=0; var started=0; var timer=null; function renderLessons(){ var u=currentUser(); if(!u)return; var ls=levels(); document.getElementById("lessonList").innerHTML= ls.map(function(l,i){ var unlocked= i===0 || u.completed.indexOf(ls[i-1].id)!==-1; var done= u.completed.indexOf(l.id)!==-1; return `
${i+1}. ${esc(l.title)} ${"★".repeat(l.stars||1)} ${done?" ✓":""} ${!unlocked?" 🔒":""}
`; }).join(""); } function selectLesson(i){ var u=currentUser(); var ls=levels(); if(i>0 && u.completed.indexOf(ls[i-1].id)===-1){ return; } currentLesson=i; loadLesson(); renderLessons(); } function loadLesson(){ clearInterval(timer); position=0; correct=0; wrong=0; started=0; var l=levels()[currentLesson]; if(!l)return; document.getElementById("lessonTitle").textContent=l.title; document.getElementById("lessonDescription").textContent=l.description; document.getElementById("hint").textContent=l.hint; document.getElementById("result").classList.add("hidden"); var box=document.getElementById("typingText"); box.innerHTML=""; for(var i=0;i0? Math.round((correct/5)/(sec/60)):0; var acc=(correct+wrong)>0? Math.round(correct/(correct+wrong)*100):100; document.getElementById("wpm").textContent=speed; document.getElementById("accuracy").textContent=acc+"%"; document.getElementById("mistakes").textContent=wrong; document.getElementById("time").textContent=Math.floor(sec); if(l){ document.getElementById("progressBar").style.width= (position/l.text.length*100)+"%"; } } function typeKey(k){ var l=levels()[currentLesson]; if(!l || position>=l.text.length)return; if(!started){ started=Date.now(); timer=setInterval(updateTyping,200); } var expected=l.text[position].toLowerCase(); var typed=String(k).toLowerCase(); var c=document.querySelector( '.char[data-index="'+position+'"]' ); if(typed===expected){ c.classList.remove("current"); c.classList.add("correct"); correct++; position++; updateTyping(); if(position>=l.text.length){ finishLesson(); } }else{ wrong++; c.classList.add("wrong"); setTimeout(function(){ c.classList.remove("wrong"); },150); updateTyping(); } } document.addEventListener("keydown",function(e){ var a=document.activeElement; if(a && ( a.tagName==="INPUT" || a.tagName==="TEXTAREA" || a.tagName==="SELECT" )){ return; } if(e.key.length===1 || e.key===" "){ e.preventDefault(); typeKey(e.key); } }); function finishLesson(){ clearInterval(timer); var l=levels()[currentLesson]; var u=currentUser(); var seconds=Math.max( .1, (Date.now()-started)/1000 ); var speed=Math.round( (correct/5)/(seconds/60) ); var acc=Math.round( correct/(correct+wrong)*100 ); if(u.completed.indexOf(l.id)===-1){ u.completed.push(l.id); u.xp+=(l.xp||20); } u.wpm=Math.max(u.wpm,speed); u.accuracy=Math.max(u.accuracy,acc); save(); document.getElementById("result").classList.remove("hidden"); document.getElementById("result").innerHTML=`

🎉 Level Complete!

${speed} WPM

Accuracy: ${acc}%

Mistakes: ${wrong}

🏆 +${l.xp||20} XP

`; renderLessons(); } function nextLesson(){ if(currentLesson'+k.toUpperCase()+"