A procedural, Montessori-inspired K–4 math game with a social-emotional learning (SEL) companion, built solo for one kid. Offline-first PWA, runs on a phone, reads itself aloud, and never serves the same problem twice.
It started on a weekend morning. Joju was three taps into a math app I had just paid for when a crown icon appeared on the button they wanted to press next, and a fifteen-second ad for another app started playing while they waited. I watched my kid's face go blank.
I counted the apps on the tablet that month. Four of them charged nine to fifteen dollars each. None of them taught anything my kid hadn't seen the week before; the same two hundred questions rotated in a new coat of paint. The "premium" content was mostly the same content, unlocked.
That night I opened a blank Vite project and started writing a generator that would never run out of problems. Joju picked the mascot. This repo is the result.
- Subscriptions and ads. The best features sit behind a crown icon, and the wait between levels is a slot for another app's ad. That model optimizes for parent retention, not for the child.
- Finite content. Even the big-name apps ship a fixed bank of a few hundred questions per topic. Kids memorize answers, not skills, and the "progression" is mostly cosmetic (new backgrounds, new avatars, same math).
- Noise. Slot-machine rewards, streaks designed to create guilt if you miss a day, and constant coin-drop sound effects make the screen feel like a casino, not a classroom.
- It does not run out. The engine in
src/engine/proceduralGenerator.tsgenerates infinite unique activities, seeded by the child's profile and the date. The same kid sees fresh content every day; a sibling on another device sees the same content. No server required. - It reads itself aloud. A pre-reader can play independently. A
local voice proxy (
server/voice-proxy.mjs) narrates every instruction through free Edge TTS by default, with Azure or OpenAI as opt-in upgrades. - It moves at Montessori pace. Three-period lessons from
src/engine/montessoriCurriculum.tsshift the instruction style — "Look!", "Show me", "What is this?" — based on how well the child already knows the topic. - It names feelings, not only numbers. The SEL mini-games in
src/engine/selEngine.tsteach emotions, kind choices, and step sequences alongside math. - Parents keep the dials. The Parent Zone mutes voice, turns off
celebrations, switches to a gentler mascot, and respects accessibility
needs (reduced motion, larger text, literal language, sensory-friendly
mode) via
src/engine/autismSupport.tsandsrc/engine/focusEngine.ts. - Nothing leaves the device unless the parent opts in. IndexedDB is the default store. Firebase sync is a toggle, not a default. No analytics SDKs, no trackers.
- vs Khan Academy Kids and ABC Mouse. Joju has no accounts, no ads, no tracking, and no paywall. You can clone the repo, run it locally, and your kid plays in five minutes.
- vs Prodigy and similar "math RPGs". Here, math is the game, not the toll you pay for another combat turn. Celebrations are bounded, streaks are soft, and nothing buzzes at you for missing a day.
- vs Duolingo ABC. Duolingo's model is spaced repetition over a fixed tree of lessons. Joju's model is a procedural generator that re-rolls from templates plus emoji pools, so a child with a hundred hours in the app has seen a hundred hours of different problems.
- vs DragonBox, Numberblocks, Khan Kids math. Those are polished and beautiful, and they all ship with a finite content bundle. Joju's bet is that the engine is the product. Swap the curriculum JSON and you have a different age group or a different language.
- vs every other kid app. Dual-licensed. Code MIT, content CC BY-NC 4.0. Teachers and homeschool parents can use, remix, and share. Companies cannot repackage the content and sell it.
flowchart LR
otherApps["Most kid apps<br/>fixed content bank"] --> grind[Child memorizes answers]
joju["Joju<br/>procedural engine"] --> fresh[Infinite unique activities]
fresh --> skills[Child builds skills, not answers]
grind --> churn[Parent cancels subscription]
- Client-side procedural generator in
src/engine/proceduralGenerator.ts(~2,600 lines): templates, emoji pools, distractor logic, quest wrappers, difficulty tiers, all seeded so offline works and two devices on the same profile agree on content. - Montessori three-period lesson flow in
src/engine/montessoriCurriculum.tswraps each instruction depending on the child's mastery level. - SEL mini-games in
src/engine/selEngine.tspower emotion-labeling, scenario choice, and sequence puzzles. - Voice pipeline with an emoji-to-word sanitizer so "🍇 fly away" is never read as "purple-heart fly away", and a proxy that falls back Edge TTS → Azure → OpenAI.
- Offline-first PWA with IndexedDB progress, optional Firebase sync, and a Parent Zone for per-topic stars, accessibility toggles, and gentle / celebration mode.
Full disclosure: this was shipped solo, is not actively maintained, and is being open-sourced as a portfolio-grade case study more than a product. See Status and limitations below.
flowchart LR
parent[Parent or child user] --> pwa[React 19 PWA<br/>Vite 8]
pwa --> engine[Procedural engine<br/>src/engine]
engine --> activities[Generated activities]
activities --> games[Game components<br/>src/components/games]
pwa --> idb[(IndexedDB progress)]
pwa --> fb[(Firebase auth and sync)]
pwa -->|TTS requests| voice[Voice proxy<br/>:8787]
voice -->|edge or OpenAI or Azure| tts[External TTS]
pwa -->|content fetch| content[Content server<br/>:8788]
Three processes run locally in dev (npm run dev starts all three via
concurrently):
| Port | Process | Source |
|---|---|---|
5173 |
Vite dev server | vite.config.ts |
8787 |
Voice (TTS) proxy | server/voice-proxy.mjs |
8788 |
Content API | server/content-server.ts |
- React 19 + TypeScript 5.9 + Vite 8
- Firebase (auth + Firestore + optional Cloud Functions) —
functions/ - IndexedDB via
idbfor offline progress - Zustand for client state
- node-edge-tts for free neural TTS; Azure Speech and Azure OpenAI as opt-in backends
- Phosphor Icons + custom SVG + a Rive mascot
- Vitest + Testing Library + happy-dom for unit tests
git clone https://github.com/<your-user>/joju-learn.git
cd joju-learn
cp .env.example .env
cp functions/.env.example functions/.env.local # only if you want Cloud Functions
npm install
npm run devOpen http://localhost:5173. You can run without a Firebase project if you
leave the VITE_FIREBASE_* values blank — the app falls back to local
IndexedDB and the child-select screen.
Useful scripts:
npm run dev # vite + voice proxy + content server
npm run dev:vite # just the Vite app (no voice, no content server)
npm run build # tsc -b && vite build
npm run preview # preview production build
npm run lint # ESLintsrc/
app/ app shell, router, providers
components/ reusable UI (auth, brand, game, games, kid, layout, monetization, parent, story)
content/ curriculum and topic definitions, strongly typed
engine/ procedural generator, SEL engine, Montessori curriculum, mascot dialogue, rewards
lib/ small helpers (Firebase client, IDB, etc.)
pages/ top-level routes (GamePage, SelActivitiesPage, SandboxPage, Parent pages, …)
services/ audio, TTS sanitization, analytics, settings, sync
store/ Zustand stores
styles/ global + per-feature CSS
server/ node processes for dev (voice proxy, content API)
functions/ Firebase Cloud Functions (LLM-backed activity generation)
public/ icons, mascot, PWA manifest
The most interesting file if you like procgen is
src/engine/proceduralGenerator.ts
(roughly 2,600 lines of templates, pools, distractor logic, quest
wrappers, and difficulty tiers).
This is a portfolio / learning release, not a product roadmap.
- Not actively maintained. Issues are welcome but may not be addressed.
- Pull requests that fix bugs or polish the existing surface are welcome;
big new features are out of scope. See
CONTRIBUTING.md. - Tested primarily on Chrome desktop and Safari iOS PWA.
- TTS quality depends on your chosen backend. Edge TTS is free and surprisingly good; Azure / OpenAI cost money and are optional.
- Firebase is optional — everything works against IndexedDB alone.
Dual-licensed, on purpose:
- Code — MIT. See
LICENSE. - Content and assets — Creative Commons Attribution-NonCommercial 4.0
International. See
LICENSE-CONTENT.
See NOTICE.md for the short plain-English version.
Built solo by Binol George for his kid Joju. The goal was always: one parent, one child, one app that makes a difficult year a bit more playful. If any piece of this is useful to you — the procedural engine, the Montessori integration, the SEL scenarios, the voice pipeline — please take it, credit the project, and build something kind with it.
If you want to reach out, open an issue or find me on LinkedIn.
