Initial commit: Zabavna Matematika web and Android app.

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.
This commit is contained in:
2026-08-15 10:59:25 +02:00
commit 6e85917f73
147 changed files with 9190 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
# Android
Zabavna Matematika on the phone is the **same web game**, running in a Capacitor WebView. Phones and tablets share one APK. Layout is CSS (already tuned for ~390px and up), not a second codebase.
## What lives where
| Path | Role |
|---|---|
| `web/src/` | The game |
| `web/dist/` | Vite production build (synced into Android) |
| `web/capacitor.config.json` | App id `net.bit76.matematika`, `webDir: dist` |
| `web/android/` | Gradle / Android Studio project (generated, then committed) |
Do not start a Kotlin/Compose game. Change play logic in `web/src/` only, then sync.
## First-time machine setup
1. Install [Android Studio](https://developer.android.com/studio).
2. Install an SDK (API 24+ is enough for Capacitor 7) and a virtual device, or plug in a phone with USB debugging.
3. Accept licenses if Studio asks.
## Commands
From the repo root or `web/`:
```bash
cd web
npm install
npm run android
```
That runs `vite build`, `cap sync android`, then `cap open android`. In Studio press Run.
After you change JS/CSS/assets:
```bash
npm run cap:sync
```
Then Run again in Android Studio. Skip a full `cap add android` unless the `android/` folder is missing.
If `android/` was never generated (clone of a repo that omitted it):
```bash
cd web
npm run build
npx cap add android
npx cap sync
```
## App identity
Set in `web/capacitor.config.json`:
- **Application id:** `net.bit76.matematika` (Play Console package name)
- **Display name:** Zabavna Matematika
Change these before the first Play Store listing. Renaming later is painful.
## Icons and splash
Replace the Capacitor placeholders in:
- `web/android/app/src/main/res/mipmap-*/`
- `web/android/app/src/main/res/drawable*/`
Keep one icon set. Tablet vs phone is the same WebView.
## Store notes
- Privacy policy: `web/public/privacy.html` (also served on the web host).
- Orientation: portrait is already declared in the PWA manifest; you can match that in `AndroidManifest.xml` if you want to lock it.
- Offline: the existing service worker still helps inside the WebView; Capacitor also ships the `dist/` files on device, so the game works without a network after install.
## What not to do
- Do not add `apps/android` with a rewritten UI.
- Do not copy `questionGenerator.js` into Java/Kotlin.
- Do not treat tablets as a separate flavor unless you later need a different Play listing.
+95
View File
@@ -0,0 +1,95 @@
# 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:
1. **Loading** — preload images / audio / the settings video, then **Nadaljuj**
2. **Settings** — name, operations, difficulty, audio, start modal
3. **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.
1. `buildSession(config, level)` in `questionGenerator.js` builds the full list.
2. `showQuestion(i)` shows item `i` from that list.
3. `resolveAnswer(given)` scores a tap or keypad OK.
4. After the last item, `GameOver` offers 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 | 110 |
| `medium` | choices | 6 | 112 |
| `hard` | keypad | — | 120 |
Negatives are off (`allowNegative: false`). The keypad hides `±` unless that flag is flipped.
Full product wording: [LEVELS.md](../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`:
1. Add a named op in `config.js` if needed.
2. Teach `buildSession` how to emit `{ op, a, b, result, text }`.
3. Add a settings toggle only when the UI exists.
4. Cover it in `questionGenerator.test.js`.
Rounding, dice, calculator, equations, and text problems were removed from the live path on purpose.
## Local checks
```bash
npm test
npm run build
```
Android store build: [ANDROID.md](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.