---
title: keyb/60
canonical: "https://presque.cool/projects/keyb60/"
description: "Un test de vitesse de frappe avec un clavier Keychron K2 virtuel, des sons mécaniques et une radio lo-fi intégrée."
---

<div data-lang="fr">

## 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/](https://presque.cool/keyb60/)

<div class="quick-overview">
  <div class="overview-item">
    <span class="overview-label">Durée</span>
    <span class="overview-value">~2 jours</span>
  </div>
  <div class="overview-item">
    <span class="overview-label">Période</span>
    <span class="overview-value">Mars 2026</span>
  </div>
  <div class="overview-item">
    <span class="overview-label">Stack</span>
    <div class="tech-tags">
      <span class="tech-tag">React</span>
      <span class="tech-tag">TypeScript</span>
      <span class="tech-tag">Zustand</span>
      <span class="tech-tag">PWA</span>
    </div>
  </div>
  <div class="overview-item">
    <span class="overview-label">Statut</span>
    <span class="overview-value accent">&#10003; En production</span>
  </div>
</div>

---

## 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

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

---

## Évolutions

| Date      | Version | Description                                      |
| --------- | ------- | ------------------------------------------------ |
| Mars 2026 | 1.0     | Version 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.

</div>

<div data-lang="en">

## 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/](https://presque.cool/keyb60/)

<div class="quick-overview">
  <div class="overview-item">
    <span class="overview-label">Duration</span>
    <span class="overview-value">~2 days</span>
  </div>
  <div class="overview-item">
    <span class="overview-label">Period</span>
    <span class="overview-value">Mar 2026</span>
  </div>
  <div class="overview-item">
    <span class="overview-label">Stack</span>
    <div class="tech-tags">
      <span class="tech-tag">React</span>
      <span class="tech-tag">TypeScript</span>
      <span class="tech-tag">Zustand</span>
      <span class="tech-tag">PWA</span>
    </div>
  </div>
  <div class="overview-item">
    <span class="overview-label">Status</span>
    <span class="overview-value accent">&#10003; In production</span>
  </div>
</div>

---

## 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

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

---

## Timeline

| Date     | Version | Description                                       |
| -------- | ------- | ------------------------------------------------- |
| Mar 2026 | 1.0     | Initial 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.

</div>
