Wat ik bouw
Ritme is mijn zelfstandige typetrainer in de browser: woorden in zes talen, cijfers en numpad, code, eigen tekst, toetsenbordbegeleiding en lokale voortgang. De nadruk ligt op nauwkeurigheid en bruikbare feedback, niet alleen op WPM.
De app houdt meten en oefenen uit elkaar. Standard v1 geeft een vast vergelijkingsprofiel; adaptieve oefeningen besteden meer aandacht aan zwakke patronen. HTML, CSS en JavaScript volstaan voor de kern. Een account of AI-provider is niet nodig.
Een typetest eindigt vaak met een groot getal. Zoveel woorden per minuut. Even kijken of het hoger is dan de vorige keer, nog een poging doen en weer verder. Maar dat getal vertelt me niet waarom een tekst stroef ging. Was ik voortdurend aan het corrigeren? Viel mijn tempo weg bij hoofdletters? Of probeerde ik vooral te snel te beginnen?
Daarom bouw ik Ritme. Een rustige typetrainer waarin snelheid een uitkomst is, niet het enige doel. Ik wil kunnen zien wat er tijdens het typen gebeurt, gericht oefenen en daarna onder vergelijkbare omstandigheden opnieuw meten.
De browserapp combineert woorden, cijfers, code en eigen tekst met toetsenbordbegeleiding, adaptieve oefeningen en lokale voortgang. Zonder account of backend voor de zelfstandige trainer. De extra mogelijkheden moeten op het juiste moment beschikbaar zijn, niet allemaal tegelijk aandacht vragen.12
Eerst nauwkeurig. Dan sneller.
Ik wil niet alleen weten hoe snel ik typ. Ik wil begrijpen waar het hapert en waar oefenen iets kan toevoegen.
Die gedachte bepaalt de interface. Een tekstveld, een duidelijke cursor en feedback die ik kan negeren totdat ik haar nodig heb. De timer begint bij mijn eerste invoer, niet terwijl ik nog een instelling zoek. Toetsen en vingerkleuren kan ik erbij halen voor een oefening en weer weglaten bij een meting.1
Ik probeer daarmee geen universeel lesprogramma of gecertificeerd examen te maken. Het is software voor een concrete behoefte: een vaardigheid die ik dagelijks gebruik met meer aandacht kunnen oefenen. De waarde zit niet in een groter dashboard, maar in een betere volgende oefening.
Hetzelfde tempo. Een ander verhaal.
Neem twee denkbeeldige tests van precies één minuut. In beide blijven driehonderd correcte tekens staan. Ritme rekent met vijf tekens per standaardwoord: beide tests leveren dus zestig WPM op. Ook de eindnauwkeurigheid kan bij allebei honderd procent zijn.1
Toch kunnen die tests heel verschillend zijn verlopen. In de eerste typ ik alles meteen goed. In de tweede maak ik twintig verkeerde invoerpogingen, die ik daarna verwijder. Dezelfde nette eindtekst, maar meer herstelwerk. Dat verschil maakt toetsaanslagnauwkeurigheid zichtbaar.
De eindtekst verandert niet. De invoer wel.
Verander het aantal gecorrigeerde fouten. Tempo en nette eindtekst blijven gelijk; de kwaliteit van de invoer wordt zichtbaar.
Vast: 60 seconden, 300 correcte eindtekens, geen overgeslagen tekens. De slider voegt uitsluitend later verwijderde fouten toe.
300 ÷ (300 + 20) × 100Rekenvoorbeeld, geen gebruikersmeting. Dezelfde nette eindtekst vertelt niet het hele verhaal.
In dit rekenvoorbeeld houd ik de duur en eindtekst gelijk. Je verandert alleen het aantal fouten dat later is gecorrigeerd. Tijd voor corrigeren zit al in de minuut; verwijderde tekens tellen niet mee als tekstproductie. Backspace en Shift zijn geen extra karakterpogingen. Zo zie je waarom ik het resultaat en de weg ernaartoe apart meet.1
Voor mij is dat een bruikbaarder vertrekpunt dan alleen een record. Ik kan besluiten rustiger te beginnen, eerst een lastige combinatie te oefenen of de begeleiding tijdelijk aan te zetten. Dat is een oefenkeuze, geen diagnose uit één test.
Meten en oefenen zijn verschillende taken.
Een test is pas een bruikbare vergelijking wanneer de omstandigheden voldoende overeenkomen. Nederlandse woorden met leestekens vragen iets anders dan Engelse woorden zonder hoofdletters. Een korte sprint is iets anders dan twee minuten doorwerken. Een ander toetsenbordprofiel of corpus verandert eveneens de opdracht.
Daarom heeft Ritme Standard · v1: zestig seconden, een vaste woordpool per taal, kleine letters en geen extra cijfers, leestekens of vingerbegeleiding. Taal, layout, corpusvingerafdruk en protocolversie blijven onderdeel van het vergelijkingsprofiel. De woordvolgorde wisselt nog steeds; een gelijk profiel is geen identieke test of officiële vaardigheidsnorm.13
Adaptief oefenen stelt juist een andere vraag: welke patronen verdienen meer herhaling? Wanneer de oefening zich aanpast, verandert haar moeilijkheid. Een WPM-record is dan niet het juiste bewijs van vooruitgang. Ritme houdt die oefenroute daarom apart van de vaste meting.1
Een meting is geen oefening.
Een vast meetprofiel ondersteunt vergelijking. Adaptieve oefening past juist de opdracht aan. Beide routes leveren feedback op, maar niet dezelfde soort record.
Alle nodes en verbindingen
Mijn praktische route is daardoor eenvoudig. Eerst onder vaste omstandigheden meten. Vervolgens oefenen op wat aandacht verdient. Daarna opnieuw hetzelfde meetprofiel gebruiken. Niet iedere oefensessie hoeft een poging te zijn om mijn hoogste score te verbeteren.
Een toetsenbord is meer dan losse woorden.
Soms wil ik gewoon woorden typen. Soms gaat het om cijfers, hoofdletters of een teken dat ik in code vaak gebruik. In Ritme zijn dat herkenbare activiteiten, geen verborgen schakelaars die ongemerkt één totaalscore veranderen.
| Situatie | Route in Ritme | Wat ik ermee oefen |
|---|---|---|
| Woorden met meer controle | Woorden of vaste toetsenrijen, optionele begeleiding | Nauwkeurige invoer en bekende bewegingen |
| Veel numerieke invoer | Cijfers → getallenrij of numpad | Cijferreeksen en een passend begeleidingsprofiel |
| Haakjes, hoofdletters en inspringing | Code → Python, HTML, CSS of JavaScript | Tekens, regeleinden en spaties; de code wordt niet uitgevoerd |
| Een specifieke passage | Eigen tekst | De aangeleverde tekst, niet een generieke benchmark |
De woordtests ondersteunen zes talen. De interface heeft Nederlands, Engels en Duits. Toetsenbordlayout is een derde, afzonderlijke keuze: US QWERTY, Duits QWERTZ of het klassieke Franse AZERTY. De interfacetaal wijzigen hoort geen lopende typetest ineens een andere opdracht te geven.13
Vingerkleuren zijn daarbij begeleiding, geen meting. Ritme kan aangeven welke toets of welke hand voor Shift wordt voorgesteld, maar ziet niet met welke vinger iemand werkelijk typt. Ook een numpadprofiel bewijst niet dat uitsluitend het fysieke numpad is gebruikt.1
In codemodus blijven regeleinden en inspringing behouden. Tab voegt vier spaties toe; er is geen automatische aanvulling. Dat maakt de opdracht concreet zonder te suggereren dat het overtypen van een fragment programmeerkennis toetst. Bij gewone eigen tekst worden herhaalde witruimtes juist genormaliseerd. Het zijn bewust verschillende invoeropdrachten.1
Meer aandacht voor wat relatief lastig is.
De toets met de meeste fouten is niet vanzelf de zwakste toets. Een letter die heel vaak voorkomt kan meer fouten verzamelen dan een zeldzame letter, terwijl het foutenpercentage lager ligt. Alleen balkjes met foutaantallen zijn dus geen voldoende basis voor adaptief oefenen.
Ritme kijkt naar fouten ten opzichte van pogingen, bij letters én letterparen. Recente vergelijkbare tests en de huidige oefening leveren signalen. Voor een patroon is eerst voldoende bewijs nodig; anders begint de oefening zonder voorkeur. De selectie richt zich op maximaal drie zwakke patronen en houdt algemene woorden in de mix.1
Een fout wordt een oefensignaal.
Geschikte tests leveren fouten en pogingen. Voldoende onderbouwde zwakke patronen krijgen extra gewicht binnen bestaande woorden; algemene oefening blijft onderdeel van de selectie.
Alle nodes en verbindingen
Voor die selectie gebruik ik een lokale heuristiek: expliciete rekenregels op fouten en pogingen. Er is geen taalmodel nodig om te kiezen uit een bestaande woordenlijst. De oefening geeft een lastig patroon vaker terug, terwijl algemene woorden afwisseling houden. Dat is een uitlegbare oefenkeuze, geen belofte van een vaste leersnelheid.1
Ik vind dat een belangrijke grens. Adaptief is hier geen marketingwoord voor een verborgen slim systeem. De invoer, weging en selectie moeten te begrijpen zijn. Wanneer een patroon verbetert, hoort zijn extra gewicht af te nemen. Wanneer informatie ontbreekt, hoort de app niet te doen alsof ze mijn zwakke plek kent.
Voortgang, zonder van iedere poging een record te maken.
De voortgangsweergave groepeert vergelijkbare tests. Afgebroken of onderbroken pogingen blijven herkenbaar in de geschiedenis, maar tellen niet zomaar mee als een volwaardige recordpoging. De klok loopt door wanneer de tab niet actief is. Dat voorkomt dat een pauze buiten beeld stilzwijgend uit het resultaat verdwijnt.1
Wie zonder klokdruk wil oefenen, kan de onbeperkte oefenmodus gebruiken. Er is live feedback en na Stop een tijdelijke samenvatting, maar geen opgeslagen score of verborgen leerprofiel voor een volgende sessie. Niet alles wat de app laat zien, hoeft duurzaam bijgehouden te worden.1
De zelfstandige trainer bewaart instellingen en maximaal honderd afgeronde tests in deze browser. Een JSON-back-up neemt meer detail mee dan CSV: onder meer instellingen, grafiekpunten en toetsstatistieken. CSV is vooral een samenvattingsexport. Dat onderscheid voorkomt de verwachting dat iedere import de volledige meetgeschiedenis kan reconstrueren.12
Lokaal betekent hier ook: niet automatisch op al mijn apparaten. Een andere browser, een ander domein of een verplaatst lokaal bestand kan een andere opslagplek hebben. Daarom hoort export bij het ontwerp. Ik wil de geschiedenis kunnen meenemen, niet moeten hopen dat browseropslag voor altijd blijft bestaan.1
Voor de technische lezers: van invoer naar oefenkeuze.
A. Eén sessie, verschillende gebeurtenissen
Ik heb de kern in gewone HTML, CSS en JavaScript gehouden. In index.html staat de interface, styles.css bepaalt de vormgeving en data.js bevat de woordenlijsten en vertalingen. De sessielogica, berekeningen en opslag staan in app.js. Zo kan ik een dataset of uitleg wijzigen zonder er een nieuwe backend naast te bouwen.23
Bovenaan app.js vergelijkt APP_RELEASE de verwachte versie met RITME_DATA.release. Ik wil voorkomen dat een gedeeltelijke upload nieuwe logica met oude data combineert. Bij een mismatch vraag ik om herladen, niet om het wissen van de voortgang.3
De eerste taalkeuze begint bij preferredInterfaceLanguage(); een opgeslagen voorkeur gaat daarna voor. De schermtaal en de oefentaal houd ik afzonderlijk bij. Daardoor kan iemand de uitleg in het Nederlands lezen en toch Engelse woorden oefenen, zonder de lopende test te veranderen.3
In de sessie houd ik invoerpogingen, overgebleven tekst, overgeslagen tekens en verstreken tijd apart. Een correctie verandert de eindtekst, maar wist de eerdere fout niet uit de aanslagnauwkeurigheid. Ook stoppen, afronden en onderbreken hebben elk een betekenis voor het resultaatprofiel. Die scheiding is nodig om voortgang eerlijk te kunnen vergelijken.1
B. Eenheden en betekenis vasthouden
WPM = correct retained characters / 5 / elapsed minutes
CPM = correct retained characters / elapsed minutes
KPH = CPM * 60
final accuracy = correct final / (retained + skipped) * 100
keystroke accuracy = correct input attempts / all input attempts * 100Ritme telt geen letterlijke woorden voor WPM en geen fysieke toetsaanslagen voor KPH. De laatste maat is correcte tekens per uur. In de grafiek zijn korte invoerpieken iets anders dan de voortschrijdende snelheid. Ook de consistentiescore heeft een expliciete eigen formule: 100 / (1 + standaardafwijking / gemiddelde), toegepast op complete secondevensters van invoerpogingen. Bij minder dan vijf complete seconden verschijnt geen score.1
C. Vaste datasets, verschillende selectieregels
De trekking vind je in secureInt(max) in app.js. Daar gebruik ik crypto.getRandomValues() met rejection sampling om een index in de dataset te kiezen. De publieke build-entropie is geen geheim; iedere trekking gebruikt nieuwe cryptografische invoer. Vaste toetsenrijoefeningen gebruiken juist herhaalbare patronen, omdat ik daar een beweging oefen in plaats van een nieuwe woordtest samenstel.3
Voor adaptief oefenen weeg ik maximaal twintig geschikte eerdere tests mee. Iedere oudere test krijgt vijftien procent minder gewicht; de huidige oefening telt dubbel. De afgevlakte foutschatting is (errors + 0.5) / (attempts + 10). Met minimale aantallen pogingen en een ondergrens voorkom ik dat één losse fout meteen de hele selectie stuurt. De rekenregels staan ook in het uitlegvenster van Ritme, zodat de keuze niet alleen vanuit de code te begrijpen is.1
D. Opslag zonder extra dienst
De opslaghelpers readLocal() en writeLocal() staan in app.js, naast de afzonderlijke sleutels voor instellingen, historie en records. Ik bewaar scores, grafiekpunten en geaggregeerde toetsstatistieken, niet de volledige getypte of geplakte tekst. Als browseropslag niet beschikbaar is, moet de interface dat melden. Een tijdelijke sessie mag niet voor bewaarde voortgang doorgaan.13
Voor de zelfstandige trainer op /ritme/ heb ik geen dienst nodig die typeresultaten ontvangt. Online vraagt je browser de appbestanden wel op bij de host. De gekoppelde Education-omgeving is een afzonderlijke toepassing met een eigen inrichting. Die scheiding is belangrijker dan het woord “lokaal” op zichzelf.1
De bouwstenen en hun herkomst.
TypeSpeedTest.com was de conceptuele en visuele inspiratie voor het rustige vertrekpunt. Ritme is een eigen implementatie met uitgebreidere oefen- en voortgangsfuncties, geen officiële uitbreiding of samenwerking met die website. Die herkomst hoort zichtbaar te blijven.24
Ook de taaldata heeft een herkomst. De basislijsten komen uit Monkeytype; de uitgebreidere selectie gebruikt daarnaast gebundelde Faker-data. Ritme vermeldt de bronnen en licenties en presenteert die woorden niet als een nationaal representatief corpus of een leeftijdsgebonden curriculum. De eigen applicatie wordt onder GPL-3.0 aangeboden.14
Hetzelfde geldt voor interpretatie van scores. Een indicatieve tempocategorie kan helpen om een getal te plaatsen. Zij is geen certificaat, persoonlijk percentiel of bewijs dat iemand in alle soorten tekst hetzelfde presteert. Ik vind het belangrijker dat een vergelijking eerlijk is dan dat de uitkomst indrukwekkend klinkt.
Openen. Oefenen. Terugkijken.
Open Ritme ↗ Bekijk de broncode ↗
Begin met één taal en een vaste meetvorm. Bekijk niet alleen het tempo, maar ook de nauwkeurigheid vóór correcties. Kies daarna een gerichte oefening. Maak een JSON-back-up wanneer je de geschiedenis wilt bewaren of meenemen. Een eenvoudige routine is waardevoller dan iedere keer een volledig andere test proberen.
De broncode kan zonder package-installatie lokaal worden gebruikt. Een kleine lokale webserver geeft voorspelbaarder browseropslaggedrag dan een steeds verplaatst HTML-bestand. Er is geen AI-sleutel nodig om woorden te oefenen of scores te berekenen.2
git clone https://github.com/fdwind1991/ritme.git
cd ritme
python3 -m http.server 8000 --bind 127.0.0.1
# Open http://127.0.0.1:8000Een klein project mag precies het juiste oplossen.
Mijn tweede brein gaat over minder context reconstrueren. Ritme gaat over iets kleiners: met meer controle kunnen typen. Toch herken ik dezelfde ontwerpkeuze. Niet de meeste functies bouwen, maar een terugkerende handeling beter laten passen bij hoe ik wil werken.
Een mooie score is prettig. Begrijpen wat ik daarna kan oefenen, is nuttiger.
Dat is voor mij de waarde van persoonlijke software. Het hoeft geen groot platform te worden om een goede reden te hebben om te bestaan. Soms is een rustig tekstveld met eerlijke feedback precies genoeg.
Wijzigingshistorie
- — Bijschriften van de illustraties verduidelijkt.
- — Herstelcontrole van de artikelrevisies afgerond.
- — Technische controle van publiceren met artikelrevisies.
Bronnen en toelichting
Code, rekenregels en datasetbronnen bij de zelfstandige Ritme-trainer. De interactieve kaart is een vast rekenvoorbeeld, geen persoonlijke testuitslag.
Gelezen op 5 oktober 2026; release 3.2.4. Betreft de zelfstandige trainer, niet de afzonderlijke Education-route. Publieke uitleg en interface, geen effectiviteitsonderzoek.
Openbare projectdocumentatie: motivatie, functionaliteit, lokale uitvoering en back-ups.
Begin bij APP_RELEASE en de controle op RITME_DATA; secureInt(max) verzorgt de trekking, preferredInterfaceLanguage() de eerste schermtaal, readLocal()/writeLocal() de opslag. Zie de uitleg in de app voor de volledige rekenregels.
Aanvullende herkomstvermelding naast de ingebouwde credits: TypeSpeedTest.com als inspiratie; taaldata met eigen bronvermeldingen.
Afbakening van dit artikel en de reproduceerbare fictieve sessie. Geen persoonlijke typeresultaten gepubliceerd.
