A framework for building rhythm-typing games for kids. Players type to the beat — keystrokes land on timing windows (Perfect/Great/Good/Miss) with satisfying particle effects and an animated reactive keyboard. Build your own game on top with a simple plugin API.
Live demo: https://typejoy.askaconsult.com
git clone https://github.com/ahrazzle/typejoy.git
cd typejoy
npm installnpm run build # bundles src/index.ts → dist/game.js (demo) + dist/bundle.js<div id="stage" style="width:900px;height:320px"></div>
<script type="module">
import { createSession } from './dist/game.js';
const session = createSession({
container: document.getElementById('stage'),
content: 'hello world',
bpm: 60,
difficulty: 'easy',
});
// That's it. The keyboard renders, approach rings appear,
// keypresses are judged, particles fly. Everything is wired.
</script>That's the entire bootstrap. createSession() wires the full pipeline (RawBus → NormalizedBus → BeatClockJudge → FeedbackLayer) in the safe order — judge wired into feedback before animation starts, timing baseline set before any key can arrive. Judgment visuals (hit flashes, approach-ring collapse, combo display, song-complete celebration) render automatically; the hooks you pass are forwarded alongside, never replaced.
const session = createSession({
container: stage,
content: 'hello world',
hooks: {
onHit: (event) => {
console.log(`${event.judgment} on "${event.key}" (delta ${event.delta}ms)`);
// Draw your game scene here
},
onCombo: (count, multiplier) => {
console.log(`Combo ${count}x (${multiplier}x)`);
},
onSongComplete: (results) => {
console.log(`Accuracy: ${results.accuracy}, Rank: ${results.ranking}`);
},
},
});session.destroy(); // stops buses, stops animation, removes keyboard DOM┌─────────────┐ ┌──────────────┐ ┌────────────────┐ ┌─────────────────┐
│ RawBus │ → │ NormalizedBus│ → │ BeatClockJudge │ → │ FeedbackLayer │
│ (keydown) │ │ (chars) │ │ (judgments) │ │ (keyboard+fx) │
└─────────────┘ └──────────────┘ └────────────────┘ └─────────────────┘
↓ hooks
your plugin
Three layers:
- Input —
RawBuscaptures keydown/keyup with high-res timestamps.NormalizedBusproduces clean character events (handles shift/caps, filters repeats). - Judge —
BeatClockJudgecompares keystrokes against the beat-map's expected notes, classifies timing into Perfect/Great/Good/Miss, tracks combo/multiplier, and emits hook events. - Feedback —
FeedbackLayerrenders the SVG keyboard, canvas particles, approach rings, combo display, and judgment stats. This is where the "feel" lives.
| Difficulty | Perfect | Great | Good | Lead-in (first ring) |
|---|---|---|---|---|
| easy | ±500ms | ±700ms | ±1000ms | 1500ms |
| medium | ±300ms | ±500ms | ±700ms | 1000ms |
| hard | ±150ms | ±300ms | ±500ms | 600ms |
| expert | ±80ms | ±150ms | ±250ms | 350ms |
| impossible | ±40ms | ±80ms | ±150ms | 250ms |
The lead-in matches the approach-ring preempt time, so the first ring is visible the moment the session starts.
- Wrong key →
onWrongKeyhook fires (gentle feedback), cursor doesn't advance, combo doesn't break - Correct key, on time → judged Perfect/Great/Good, cursor advances, combo builds
- Correct key, too early/late → ignored during lead-in; miss after the window closes
- Note passes without being hit →
onNoteStale, cursor advances
All comparisons are case-insensitive (toLowerCase() on both sides). Kids with caps lock on, or who capitalize the first letter, hit the note normally.
- Plugin Development Guide — build a custom game on the framework
- API Reference — every exported class, method, and type
- Example Plugin Walkthrough — step-by-step game built from scratch
- Contributing — dev setup, tests, conventions, and how to open a PR
- Issues & feature requests — open a GitHub issue; label it
enhancementfor a feature. - Security — see SECURITY.md for the private reporting process and trust model.
- Code of Conduct — participation is governed by CODE_OF_CONDUCT.md.
- Changelog — see CHANGELOG.md for a version history.
npm test # all four suites: 46 event-bus + 51 generator + 37 judge-regression + 2 integration tests (136 assertions)
npm run typecheck # TypeScript strict check
npm run docs # verify API_REFERENCE.md signatures against src/MIT — see LICENSE.