Kids math practice (SL/EN) in a Vite React app, wrapped with Capacitor for Android (net.bit76.matematika). Includes session refactor, i18n, and text-free difficulty icons with translated overlays.
3.3 KiB
Development
The playable app lives in web/. Stack: React 18, Vite 5, Tailwind 3. Android is the same app inside a Capacitor WebView (web/android/). No TypeScript, no backend.
Screens
App.jsx switches three states:
- Loading — preload images / audio / the settings video, then Nadaljuj
- Settings — name, operations, difficulty, audio, start modal
- Game — one session, then game-over overlay
Audio prefs persist as matematika_music. Game settings persist as matematika_settings. Language persists as matematika_lang (sl | en).
UI copy lives in web/src/i18n/sl.json and en.json. Screens call t('key') from useI18n(). Difficulty is stored as easy / medium / hard, never as a translated label. To add a language, add a JSON file with the same keys, register it in i18n/core.js, and add a button in Settings.
Session loop
Game.jsx only runs a session. It does not invent operands.
buildSession(config, level)inquestionGenerator.jsbuilds the full list.showQuestion(i)shows itemifrom that list.resolveAnswer(given)scores a tap or keypad OK.- After the last item,
GameOveroffers restart (rebuilds the pool) or back to settings.
Question index is a ref, so restart cannot immediately re-trigger game-over from a stale counter.
Each question looks like { op, a, b, result, text }.
op is one of add | sub | mul | div (src/config.js).
Difficulty vs settings
- Settings choose operations, question count, add/sub range (
max_number), multiply factors, divide divisors. - Difficulty (
src/levels.js) chooses input UI and some caps:
| Key | Input | Choices | Multiply other-factor cap |
|---|---|---|---|
easy |
choices | 4 | 1–10 |
medium |
choices | 6 | 1–12 |
hard |
keypad | — | 1–20 |
Negatives are off (allowNegative: false). The keypad hides ± unless that flag is flipped.
Full product wording: LEVELS.md.
Source map
web/src/
App.jsx
main.jsx boot + service worker register
config.js defaultConfig, OPS, mergeConfig
levels.js easy / medium / hard
questionGenerator.js + − × ÷ factory + distractors
loadAssets.js
hooks/useAnimatedBackgrounds.js
components/
Loading.jsx
Settings.jsx
Game.jsx session only
AnswerChoices.jsx
Keypad.jsx
GameQuestionCard.jsx
GameToolbar.jsx
GameOver.jsx
ExitConfirm.jsx
BackgroundMusic.jsx
ToastContainer.jsx
UpdateBanner.jsx
NumberPills.jsx
public/sw.js caches the shell using paths relative to its own scope (safe with Vite base: './').
Adding an operation later
Keep generation out of Game.jsx:
- Add a named op in
config.jsif needed. - Teach
buildSessionhow to emit{ op, a, b, result, text }. - Add a settings toggle only when the UI exists.
- Cover it in
questionGenerator.test.js.
Rounding, dice, calculator, equations, and text problems were removed from the live path on purpose.
Local checks
npm test
npm run build
Android store build: ANDROID.md.
Optional Playwright smoke (scripts/smoke.mjs) walks loading → easy → restart → medium (wrong then right) → hard keypad (empty OK disabled, no ±) → a 390×844 viewport.