No description
  • JavaScript 78.3%
  • CSS 14%
  • HTML 4.2%
  • Swift 1.9%
  • Java 1.5%
  • Other 0.1%
Find a file
rutger bb52cb68b6
All checks were successful
Build & Release Android / build (push) Successful in 2m48s
Build & Release iOS / build (push) Successful in 6m33s
Merge pull request 'rutdev' (#7) from rutdev into main
Reviewed-on: #7
2026-08-16 12:42:21 +00:00
.forgejo/workflows iOS: draai hem op de Simulator en controleer of opslaan werkt 2026-08-05 16:28:04 +02:00
app Release notes — StarTrails 2026-08-16 14:29:06 +02:00
assets/verkeersborden Release notes — StarTrails 2026-08-16 14:29:06 +02:00
css screens 2026-08-16 14:40:26 +02:00
gradle/wrapper Fixes, uitbreidingen en policy fixes 2026-07-30 10:27:20 +02:00
ios iOS: draai hem op de Simulator en controleer of opslaan werkt 2026-08-05 16:28:04 +02:00
js Release notes — StarTrails 2026-08-16 14:29:06 +02:00
media screens 2026-08-16 14:40:26 +02:00
.gitattributes Upload files to "/" 2026-07-16 20:35:30 +00:00
.gitignore iOS: dezelfde game, een eigen wrapper en een eigen pipeline 2026-08-05 15:53:59 +02:00
build.gradle Fixes, uitbreidingen en policy fixes 2026-07-30 10:27:20 +02:00
gradle.properties Android WebView-wrapper + Forgejo APK/AAB-pipeline 2026-07-17 08:39:57 +02:00
gradlew Android WebView-wrapper + Forgejo APK/AAB-pipeline 2026-07-17 08:39:57 +02:00
gradlew.bat Android WebView-wrapper + Forgejo APK/AAB-pipeline 2026-07-17 08:39:57 +02:00
index.html Release notes — StarTrails 2026-08-16 14:29:06 +02:00
privacy.html a 2026-07-23 10:22:31 +02:00
README.md Fixes, uitbreidingen en policy fixes 2026-07-30 10:27:20 +02:00
settings.gradle rename 2026-07-23 09:17:21 +02:00

StarTrails ✦

Educatief leerspel voor kinderen uit groep 2 t/m 6, met 19 leerpaden (rekenen, taal, spelling, topografie, geschiedenis, verkeer, vreemde talen, ...). Geen build, geen dependencies: index.html + css/ + js/, direct te openen in een browser. Werkt ook volledig offline (geen netwerkverzoeken).

Ook verpakt als Android-app: een dunne WebView-wrapper (zie Android-app hieronder).

Concept

De speler kiest eerst de app-taal (nl/en/es — de systeemtaal staat al voorgeselecteerd), maakt daarna een eigen diertjes-karakter, kiest een leerpad (vak) en bewandelt per wereld een slingerpad met 12 levels. Per level 5 vragen, 13 sterren. Na X gehaalde levels win je een nieuw kledingstuk voor je karakter — er zijn tientallen stuks te winnen, verspreid over een lange progressie, zodat er ook op de lange termijn iets te halen blijft.

  • Leerpaden gescheiden — elk vak heeft zijn eigen vragenbank, ze lopen nooit door elkaar
  • Groep 2 en 3 lezen nog niet — de vraag staat altijd wél uitgeschreven op het scherm (voor de ouder/leraar die meekijkt), maar wordt voor deze groepen ook automatisch en duidelijk voorgelezen; het antwoord bestaat nooit uit alleen leestekst
  • Cito-leerlijn — de werelden zijn de leerroute door het schooljaar
  • Profiel per kind — eigen taal, groep, avatar en voortgang; meerdere profielen per apparaat, elk met eigen profielbeheer (resetten/wissen)

Structuur

Blok Waar Wat
CSS css/style.css merkkleuren als CSS-variabelen, thema per wereld
Markup index.html schermen (.screen), er is er steeds één .active
Logica js/*.js + js/vakken/*.js configuratie, avatar, schermen, per-vak vraaggeneratoren

Schermen

taalkeuze → profielen → maker → vak → werelden → kaart → spel → resultaat Navigatie loopt via toon(id). De 🏠-knop (naarHuis()) staat op elk speelscherm. De taalkeuze wordt maar één keer getoond — daarna herkent de app dat de apparaattaal al gekozen is (opgeslagen in localStorage) en start hij direct bij het profielenscherm.

Uitbreiden

Alle content staat als configuratie bovenin het script. Je hoeft zelden logica aan te raken.

Nieuwe wereld

Voeg een object toe aan WERELDEN:

{ id:"jungle", icoon:"🌴", levels:12, bonus:4,
  naam:{nl:"De Wilde Jungle", en:"The Wild Jungle"},
  stijl:{ bg1:"…", bg2:"…", bg3:"…", pad:"…", padStip:"…", titel:"…", band:"…" },
  dieren:["🐒","🦜",  12 stuks],
  deco:["🌺","🍌", ],
  critters:["🦋","🐝", ] }

Het pad wordt automatisch getekend voor elk aantal levels. Voor een eigen getekende achtergrond: voeg een if (s === 'jungle')-tak toe aan sceneSVG(). Zonder die tak valt hij terug op de ruimte-scène — dus wél toevoegen.

Nieuw leerpad (vak)

  1. Object toevoegen aan VAKKEN
  2. Doelen per groep toevoegen in LEERLIJN
  3. Generatorfunctie schrijven die {tekst, spraak, picto, opties, antwoord} teruggeeft
  4. Regel toevoegen in vraagVoorVak()

Opties maak je met O(html, waarde). Voor groep 23: gebruik plaatjes als opties, nooit geschreven woorden, en zet altijd een picto en spraak.

Nieuwe kleding / milestone

Regel toevoegen aan ITEMS in js/avatar.js (slot hoofd/gezicht/nek/rug) en aan MILESTONES (op de juiste plek qua n, de lijst moet oplopend gesorteerd blijven — checkMilestones() pakt anders niet de eerstvolgende onvervulde beloning). Een item zonder eigen tekenwerk in avatarSVG() wordt automatisch als emoji op de juiste plek getekend (zie de generieke fallback onderin avatarSVG()) — voor een net iets mooiere avatar kan je er later alsnog een eigen vector-tekening aan toevoegen, net als bij de originele 8 items (pet, strik, zonnebril, ...).

Nieuwe taal

Sleutel toevoegen aan UI in js/data.js (kopieer UI.nl en vertaal) en, per vak, aan de eigen woordenbank in dat vak-bestand. Niet elk vak heeft al een volledige es-bank (sommige, zoals de Nederlandse d/t-spelling in spelling.js, hebben een taalkundig eigen — geen letterlijk vertaald — Engels alternatief en vallen voor overige talen terug op Nederlands; dat is een bewuste, gedocumenteerde keuze, geen bug). Vergeet de vlagknop in tekenMaker() niet als je een vierde taal toevoegt. Spraak: lees() in js/audio.js kiest de stem op basis van taalNu() (of een expliciete spraakTaal op de vraag, zoals het vak vreemde talen gebruikt om een woord in de andere taal met de juiste stem te laten uitspreken).

Leerlijn (Cito)

De werelden vormen de route door het schooljaar: Bos ≈ start · Strand ≈ richting M-toets · Stad ≈ richting E-toets · Ruimte ≈ verdieping

Groep Rekenen Taal
2 resultatief tellen 5→12, meer/minder luisterwoordenschat, rijmen, klankbewustzijn
3 M3 erbij/eraf t/m 10 · E3 t/m 20 + splitsen letters, hakken en plakken
4 t/m 100, tafels 1/2/5/10 klankzuivere woorden, ei/ij, au/ou
5 alle tafels, delen, t/m 1000 open/gesloten lettergrepen
6 tafels t/m 12, deelsommen, grote getallen werkwoordspelling d/dt/t

Aanpassen doe je in het LEERLIJN-object; de generatoren lezen daaruit.

Profielbeheer

Bij het bewerken van een bestaand profiel (⚙️ aankleden) staat onderin een profielbeheer-sectie:

  • Voortgang resetten — alle sterren, gewonnen items en gedragen kleding van dat profiel worden gewist (met bevestiging), de rest (naam/avatar/taal) blijft staan.
  • Alles ontgrendelen — zet in één keer alle werelden/levels op 3 sterren en ontgrendelt alle kledingstukken. Vooral bedoeld om snel te testen hoe laatste werelden/beloningen eruitzien, niet voor gewoon speelgebruik.
  • Profiel wissen — verwijdert het profiel volledig.

Elk van deze acties vraagt eerst bevestiging (native confirm()-dialoog) en is niet ongedaan te maken.

Profielen die zijn opgeslagen vóórdat een vak of wereld werd toegevoegd, worden bij het laden automatisch aangevuld (migreerProfiel() in js/app.js) zodat een oud profiel niet crasht op een nieuw vak.

Beloningssysteem

ITEMS (in js/avatar.js) bevat tientallen kledingstukken/accessoires over vier slots (hoofd, gezicht, nek, rug), die één voor één ontgrendelen via MILESTONES naarmate een profiel meer levels haalt (gehaaldeLevels(), geteld over alle vakken samen). De thresholds lopen bewust op tot ver in de honderden levels, zodat er ook voor een fanatieke speler nog lang iets te winnen is.

De originele 8 items hebben een eigen handgetekende SVG in avatarSVG(). Nieuwere items hebben dat niet — ze worden generiek als emoji op de juiste plek getekend (zie de fallback onderin avatarSVG()). Functioneel werkt dit identiek; het is alleen minder verzorgd dan de handgetekende items. Openstaand voor een latere sessie: per item een eigen vector-illustratie tekenen (zoals bij de originele 8), en evt. nog een 5e kledingslot.

Nog te doen richting productie

  • Spraak voor groep 2/3 — nu synthetische stemmen (browser speechSynthesis in de webversie, native Android TextToSpeech in de app — zie hieronder). Voor een echt kindvriendelijke ervaring: ingesproken audio van een stemacteur.
  • Geluid — nu synthetische tonen via Web Audio. Vervangen door echte samples.
  • Illustraties — emoji zijn placeholders; per platform zien ze er anders uit. Vervangen door eigen tekeningen in dezelfde huisstijl (zie ook Beloningssysteem hierboven).
  • Ouderscherm — voortgang per vak, tijdslimiet, ouderslot.
  • AVG — geen dataverzameling van kinderen, geen advertenties, offline werkend.

Android-app (build & release)

De game is een zelfstandige webapp; de Android-app is een dunne WebView-wrapper die index.html + css/ + js/ mee in de APK bundelt en volledig offline draait (geen netwerk-permissie, geen dataverzameling — AVG-vriendelijk).

Structuur

Pad Wat
app/ Android-module: één MainActivity met een full-screen WebView
app/build.gradle bundelt de webbestanden via de copyWebAssets-task naar assets/www/
settings.gradle voegt de app-module alleen toe als er een Android-SDK is (ANDROID_HOME), zodat index.html openen zónder toolchain blijft werken
.forgejo/workflows/android.yaml CI: bouwt en publiceert een getekende APK + AAB

App-id com.startrails.app · minSdk 24 (Android 7.0+) · compile/target 36.

Voorlezen (native TTS-bridge)

De Android WebView implementeert de Web Speech API (speechSynthesis) niet — die bestaat alleen in echte browsers. Zonder bridge deed de voorleesknop in de app dus he­lemaal niets. MainActivity.java initialiseert daarom Android's eigen TextToSpeech en injecteert die als window.AndroidTTS (speak(tekst, taal) / stop()) in de WebView.

js/audio.js (lees()) doet feature-detectie: is window.AndroidTTS aanwezig (in de app), dan gaat het voorlezen daarlangs; anders (in een gewone browser) via speechSynthesis, precies zoals voorheen. Dezelfde vragen-code werkt dus ongewijzigd op beide platformen.

Releasen

Elke commit naar main levert automatisch een getekende release op (continuous delivery). De pipeline bouwt app:assembleRelease + app:bundleRelease, ondertekent met de upload-key en hangt StarTrails-vX.Y.Z.apk (sideload) + .aab (Play Store) aan een nieuwe release. Je hoeft dus niets extra's te doen: gewoon pushen naar main.

  • Versie: versionName = 1.0.<CI-runnummer>, auto-tag v1.0.<runnummer>. versionCode = runnummer (uniek + oplopend, zodat Play elke upload accepteert).
  • Expliciete versie (optioneel): push een tag, bijv. git tag v2.0.0 && git push origin v2.0.0 → dan is versionName gelijk aan de tag.
  • Rookproef zonder release: workflow_dispatch op een feature-branch bouwt wél APK + AAB maar publiceert géén release.

De release-secrets staan al in Forgejo (repo → Settings → Actions → Secrets): ANDROID_KEYSTORE_BASE64, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD. Zonder deze zou CI wél bouwen, maar debug-getekend.

Lokaal bouwen kan ook met een Android-SDK: ./gradlew app:assembleDebug (APK in app/build/outputs/apk/debug/).

Nog te doen richting productie (Android)

  • Lettertypesindex.html haalt Fredoka/Nunito van Google Fonts; offline valt dit terug op systeemfonts. Voor de app: fonts lokaal meebundelen.
  • Play App Signing gebruiken; upload-key veilig bewaren.

Licentie

Intern prototype.