presque.cool
Back to projects
keyb/60 icon

keyb/60

Mar 2026 Game
Visit project

A typing speed test with a virtual Keychron K2 keyboard, mechanical sounds and a built-in lo-fi radio.


Le pitch

Keyb60 est un test de vitesse de frappe minimaliste. On tape un extrait de littérature classique, un chrono tourne, et un clavier Keychron K2 virtuel répond à chaque touche. Sons mécaniques, radio lo-fi en fond, six thèmes de keycaps, mode infini ou chronomètre.

Essayer : presque.cool/keyb60/

Durée ~2 jours
Période Mars 2026
Stack
React TypeScript Zustand PWA
Statut ✓ En production

Pourquoi ce projet

Au collège, en cours de “technologie”, notre prof nous a fait découvrir la frappe à l’aveugle. Un logiciel basique, un clavier à l’écran, des mots qui défilent. Ça m’avait marqué.

Des années plus tard, je tape quotidiennement sur un Keychron K2. Un 75%, compact, avec un son sec satisfaisant. Un soir j’ai voulu reconstruire cette expérience de cours d’informatique, mais avec mon clavier à l’écran. Quelque chose qui aurait le feeling ASMR d’un vrai clavier mécanique sous les doigts.

Deux objectifs :

  1. Reproduire la sensation : le clavier, les sons, la concentration.
  2. Faire un projet complet en un weekend : périmètre serré, exécution rapide.

Le clavier virtuel : un Keychron K2 en CSS

C’est la pièce centrale. Une reproduction fidèle du layout 75% du K2 :

  • 6 rangées avec les bonnes largeurs de touches (Shift 2.25U, espace 6.25U, etc.)
  • Touche Enter ISO en L pour le mode AZERTY, avec un clip-path pour la forme
  • Effet 3D : chaque touche a un “shell” extérieur et une face intérieure, avec des lignes de bevel pour simuler le relief
  • Animation de pression : 5px d’enfoncement au clic, transition CSS de 100ms
  • 6 thèmes de keycaps : classic, mint, royal, dolch, sand, scarlet, chacun avec trois variantes (accent, dark, light)

Le clavier s’adapte à toutes les tailles d’écran via un ResizeObserver qui ajuste un facteur --kb-scale en temps réel. Le rendu reste en pixels fixes (unité de 50px par touche), mis à l’échelle avec transform: scale().


Deux langues, deux claviers

Quand on passe en français, le clavier bascule en AZERTY. Pas juste les labels : le layout complet change. Positions des lettres, caractères spéciaux, touche supplémentaire < à gauche du Shift, Enter ISO à la place de l’ANSI.

Les textes de frappe aussi changent : 10 extraits en anglais (Dickens, Melville, Austen…), 10 en français (Hugo, Camus, Proust…). Du domaine public, choisis pour leur rythme et leur longueur.


Le son

Deux couches audio :

Les clics mécaniques. Enregistrement d’un vrai clavier mécanique, découpé en sprite audio. Six variantes pour le “key down”, six pour le “key up”, choisies aléatoirement à chaque frappe. Le son d’erreur utilise un jeu de clips différent. Le tout via Web Audio API pour une latence minimale.

La radio. Trois stations de lo-fi/ambient en streaming (Dreamscapes, Chillwave, Lofi Radio). On les contrôle via les touches de fonction du clavier virtuel ou le panneau de settings. Volume indépendant des sons de frappe.

Au final on se retrouve à taper sur du Dickens avec un bruit de Cherry MX et du lo-fi dans les oreilles. C’est agréable.


Touches de fonction

Chaque touche F du clavier virtuel a une fonction :

  • F3 : Partager les résultats
  • F4 : Ouvrir les settings
  • F5 : Basculer EN/FR
  • F6 : Basculer dark/light
  • F7/F9 : Station précédente/suivante
  • F8 : Toggle radio
  • F10 : Toggle sons
  • F11/F12 : Volume -/+
  • F14 : Changer le thème du clavier
  • Delete : Reset

Tout passe par le clavier : les réglages, la navigation entre stations, le partage. On peut utiliser l’app sans jamais ouvrir le panneau de settings.


Stack technique

  • React + TypeScript
  • Vite comme bundler (avec SWC)
  • Zustand pour le state management — persistance localStorage (paramètres) + IndexedDB (progression)
  • Tailwind CSS en mode CSS-first (tokens OKLCH)
  • Web Audio API pour les sons mécaniques
  • PWA : installable, offline
  • i18n maison (français/anglais)
  • Compression : Brotli + Gzip sur les assets
  • CSP : Content-Security-Policy avec hashes auto-générés

Évolutions

DateVersionDescription
Mars 20261.0Version initiale : clavier K2, sons, radio, i18n

Bilan

Durée : 2 jours.

Ce que j’ai appris :

  • Construire un composant visuel complexe en CSS pur (le clavier 3D avec ses 6 thèmes et le Enter ISO en clip-path)
  • Web Audio API et la gestion de sprites audio pour une latence minimale
  • Que la différence entre “ça marche” et “ça fait plaisir” tient à des détails très précis : 100ms de transition sur l’enfoncement des touches, la randomisation des samples de clic, le fade du texte en haut de la zone de frappe

Ce que je referais pareil :

  • Périmètre serré, exécution rapide. Deux jours, pas plus. Ça force à faire des choix.

Ce que je changerais :

  • Explorer le retour haptique via l’API Vibration sur mobile, pour rapprocher l’expérience du toucher réel d’un clavier mécanique.

The pitch

Keyb60 is a minimalist typing speed test. You type a classic literature excerpt, a timer counts down, and a virtual Keychron K2 keyboard responds to every keystroke. Mechanical sounds, lo-fi radio in the background, six keycap themes, infinite or timed mode.

Try it: presque.cool/keyb60/

Duration ~2 days
Period Mar 2026
Stack
React TypeScript Zustand PWA
Status ✓ In production

Why this project

In middle school, our “technology” teacher introduced us to touch typing. A basic program, a keyboard on screen, words scrolling by. It stuck with me.

Years later, I type daily on a Keychron K2. A 75% layout, compact, with a satisfying dry click. One evening I wanted to rebuild that school computer lab experience, but with my keyboard on screen. Something that would feel like a real mechanical keyboard under your fingers.

Two goals:

  1. Recreate the feeling: the keyboard, the sounds, the focus.
  2. Build a complete project in a weekend: tight scope, fast execution.

The virtual keyboard: a Keychron K2 in CSS

This is the centerpiece. A faithful reproduction of the K2’s 75% layout:

  • 6 rows with proper key widths (Shift 2.25U, space 6.25U, etc.)
  • ISO Enter key in L-shape for AZERTY mode, using clip-path for the shape
  • 3D effect: each key has an outer shell and an inner face, with bevel lines to simulate depth
  • Press animation: 5px compression on click, 100ms CSS transition
  • 6 keycap themes: classic, mint, royal, dolch, sand, scarlet, each with three variants (accent, dark, light)

The keyboard adapts to all screen sizes via a ResizeObserver that adjusts a --kb-scale factor in real-time. Rendering stays in fixed pixels (50px per key unit), scaled with transform: scale().


Two languages, two keyboards

When switching to French, the keyboard flips to AZERTY. Not just the labels: the entire layout changes. Letter positions, special characters, extra < key left of Shift, ISO Enter replacing the ANSI one.

The typing passages change too: 10 English excerpts (Dickens, Melville, Austen…), 10 French ones (Hugo, Camus, Proust…). All public domain, chosen for their rhythm and length.


The sound

Two audio layers:

Mechanical clicks. Recorded from a real mechanical keyboard, sliced into an audio sprite. Six variants for key down, six for key up, randomly chosen on each keystroke. Error sounds use a different set of clips. All through Web Audio API for minimal latency.

The radio. Three lo-fi/ambient stations streaming live (Dreamscapes, Chillwave, Lofi Radio). Controlled via the virtual keyboard’s function keys or the settings panel. Volume independent from typing sounds.

You end up typing Dickens to the sound of Cherry MX switches and lo-fi beats. It’s pleasant.


Function keys

Each F key on the virtual keyboard has a purpose:

  • F3: Share results
  • F4: Open settings
  • F5: Toggle EN/FR
  • F6: Toggle dark/light
  • F7/F9: Previous/next station
  • F8: Toggle radio
  • F10: Toggle sounds
  • F11/F12: Volume -/+
  • F14: Cycle keyboard themes
  • Delete: Reset

Everything goes through the keyboard: settings, station switching, sharing. You can use the app without ever opening the settings panel.


Tech stack

  • React + TypeScript
  • Vite as bundler (with SWC)
  • Zustand for state management — localStorage (settings) + IndexedDB (progression) persistence
  • Tailwind CSS in CSS-first mode (OKLCH tokens)
  • Web Audio API for mechanical sounds
  • PWA: installable, offline
  • i18n custom (French/English)
  • Compression: Brotli + Gzip on assets
  • CSP: Content-Security-Policy with auto-generated hashes

Timeline

DateVersionDescription
Mar 20261.0Initial release: K2 keyboard, sounds, radio, i18n

Takeaways

Duration: 2 days.

What I learned:

  • Building a complex visual component in pure CSS (the 3D keyboard with its 6 themes and the ISO Enter clip-path)
  • Web Audio API and audio sprite management for minimal latency
  • That the difference between “it works” and “it feels good” comes down to very specific details: 100ms transition on key press, randomizing click samples, the text fade at the top of the typing area

What I’d do the same:

  • Tight scope, fast execution. Two days, no more. It forces you to make choices.

What I’d change:

  • Explore haptic feedback via the Vibration API on mobile, to bring the experience closer to the real feel of a mechanical keyboard.

Settings

Language
Theme
Privacy Policy