From dadcbdbcbb6dc3b743699c87509b89d2d2b6191c Mon Sep 17 00:00:00 2001 From: OKI Date: Mon, 27 Jul 2026 11:53:22 -0400 Subject: [PATCH] =?UTF-8?q?feat(session-2):=20moteur=20p=C3=A9dagogique=20?= =?UTF-8?q?en=20Svelte=20pur=20=E2=80=94=20Kach=C3=A9,=20S=C3=A9zon,=20Rek?= =?UTF-8?q?on=C3=A8t,=20Lakou?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trois modes, l'album et la progression locale. Aucun Phaser. Kaché est pleinement jouable : indices dévoilés un à un (feuille → écorce → port → coupe → fruit), score dégressif de 100 à 20. Il ne dépend d'aucune photographie, ce qui le rend jouable aujourd'hui alors qu'aucune image n'est validée. Les distracteurs sont tirés par proximité botanique — un corossol face à une pomme-cannelle enseigne la vraie confusion, deux Annona qu'on confond réellement. Sézon est jouable et affiche en permanence que sa saisonnalité n'est sourcée nulle part (a-arbitrer §3). Signalé, pas caché. Rekonèt est complet mais bloqué : 0 photo validée sur 8 espèces. Il affiche un état designé qui dit pourquoi, et pose la règle n°1 là où un enfant la lira — il ne se rabattra jamais sur une image générée. Répétition espacée Leitner à 5 boîtes dans localStorage, tirage pondéré 8 contre 1 vers les boîtes basses. Vérifié en jouant : bonne réponse → boîte 2 et fiche débloquée, erreur → retour boîte 1. Export/import JSON, aucun compte, aucune donnée qui quitte l'appareil. use:compte pour le moment de révélation : rAF → DOM direct, span animé aria-hidden, valeur finale en .sr-only dans une région aria-live. Mesures - 65,6 Ko de JS gzip (budget 170) · 68 pages prérendues - npm run check : 259 fichiers, 0 erreur, 0 warning - elementFromPoint à 375 px : 0 recouvrement, 0 cible sous 44 px - ordre de tabulation naturel, aucun tabindex positif Défaut trouvé en testant : le fil d'Ariane faisait 16 px de haut sur mobile. Corrigé — un lien seul sur sa ligne est une commande, pas un lien de texte. La porte « le jeu est-il amusant sans Phaser » reste ouverte : elle demande de jouer, et elle appartient au porteur. Co-Authored-By: Claude Opus 5 --- docs/sessions/02-moteur-pedagogique.md | 98 ++++++ src/lib/jeu/progression.svelte.ts | 130 ++++++++ src/lib/jeu/tirage.ts | 42 +++ src/lib/motion/countup.ts | 62 ++++ src/lib/styles/base.css | 14 + src/routes/[[locale=locale]]/je/+page.svelte | 35 +-- .../[[locale=locale]]/je/kache/+page.svelte | 291 ++++++++++++++++++ .../[[locale=locale]]/je/lakou/+page.svelte | 202 ++++++++++++ .../[[locale=locale]]/je/rekonet/+page.svelte | 276 +++++++++++++++++ .../[[locale=locale]]/je/sezon/+page.svelte | 284 +++++++++++++++++ 10 files changed, 1413 insertions(+), 21 deletions(-) create mode 100644 docs/sessions/02-moteur-pedagogique.md create mode 100644 src/lib/jeu/progression.svelte.ts create mode 100644 src/lib/jeu/tirage.ts create mode 100644 src/lib/motion/countup.ts create mode 100644 src/routes/[[locale=locale]]/je/kache/+page.svelte create mode 100644 src/routes/[[locale=locale]]/je/lakou/+page.svelte create mode 100644 src/routes/[[locale=locale]]/je/rekonet/+page.svelte create mode 100644 src/routes/[[locale=locale]]/je/sezon/+page.svelte diff --git a/docs/sessions/02-moteur-pedagogique.md b/docs/sessions/02-moteur-pedagogique.md new file mode 100644 index 0000000..41d322c --- /dev/null +++ b/docs/sessions/02-moteur-pedagogique.md @@ -0,0 +1,98 @@ +# Session 2 — Moteur pédagogique, en Svelte pur + +**Date :** 2026-07-27 · **Livrable :** trois modes de jeu, l'album, la progression locale. + +## La porte de sortie, honnêtement + +| Critère | Cible | Mesuré | +|---|---|---| +| **Le jeu est amusant sans Phaser** | jugement humain | **à trancher par le porteur** — voir plus bas | +| Navigation clavier | complète | contrôles ` + + +

+

{messageImport}

+ + + + diff --git a/src/routes/[[locale=locale]]/je/rekonet/+page.svelte b/src/routes/[[locale=locale]]/je/rekonet/+page.svelte new file mode 100644 index 0000000..fced4a4 --- /dev/null +++ b/src/routes/[[locale=locale]]/je/rekonet/+page.svelte @@ -0,0 +1,276 @@ + + + + +
+

{t(locale, 'jeu.quitter')}

+ + {#if questions.length === 0} + +
+ +

{t(locale, 'jeu.indisponible.titre')}

+

{t(locale, 'jeu.indisponible.texte')}

+

+ {jouables.length} espèce{jouables.length > 1 ? 's' : ''} sur {FRUITS.length} + dispose d’une photographie validée. Il en faut au moins quatre pour poser une question + honnête. +

+

+ Ce mode fonctionne uniquement sur des photographies réelles, validées par un humain. Il ne + se rabattra jamais sur une image générée : ce serait apprendre à reconnaître ce qu’un + modèle invente. +

+

+ {t(locale, 'manques.aider')} + {t(locale, 'jeu.kache')} +

+
+ {:else if fini} +
+

{t(locale, 'jeu.termine')}

+

+ + {score} +

+

{t(locale, 'jeu.resultat', { bons: bonnes, total: questions.length })}

+

+ + {t(locale, 'jeu.album')} +

+
+ {:else if q} +
+

{t(locale, 'jeu.rekonet')}

+

{index + 1} / {questions.length} — {t(locale, 'jeu.score')} : {score}

+
+ +
+ {t(locale, +
+ {q.photo.auteur} — {q.photo.licence} +
+
+ +
+

{t(locale, 'jeu.question')}

+
    + {#each q.choix as choix (choix.id)} +
  • + +
  • + {/each} +
+
+ +
+ {#if reponse} +

+ {correct ? t(locale, 'jeu.bonne-reponse') : t(locale, 'jeu.mauvaise-reponse')} +

+

{t(locale, 'jeu.reponse-etait', { nom: q.cible.noms.gcf })}

+

+ + {t(locale, 'zerbaj.voir')} +

+ {/if} +
+ {/if} +
+ + diff --git a/src/routes/[[locale=locale]]/je/sezon/+page.svelte b/src/routes/[[locale=locale]]/je/sezon/+page.svelte new file mode 100644 index 0000000..3f6887a --- /dev/null +++ b/src/routes/[[locale=locale]]/je/sezon/+page.svelte @@ -0,0 +1,284 @@ + + + + +
+

{t(locale, 'jeu.quitter')}

+ + +

+ {t(locale, 'fiche.saison-non-sourcee')} + {t(locale, 'manques.aider')} +

+ + {#if !questions.length} +

{t(locale, 'jeu.indisponible.texte')}

+ {:else if fini} +
+

{t(locale, 'jeu.termine')}

+

+ + {score} +

+

{t(locale, 'jeu.resultat', { bons: bonnes, total: questions.length })}

+

+ + {t(locale, 'jeu.quitter')} +

+
+ {:else if q} +
+

{t(locale, 'jeu.sezon')}

+

{index + 1} / {questions.length} — {t(locale, 'jeu.score')} : {score}

+
+ +
+

+ Quel fruit trouve-t-on en {MOIS[q.mois - 1]} ? +

+ + + +
    + {#each q.choix as choix (choix.id)} +
  • + +
  • + {/each} +
+
+ +
+ {#if reponse} +

+ {correct ? t(locale, 'jeu.bonne-reponse') : t(locale, 'jeu.mauvaise-reponse')} +

+

{t(locale, 'jeu.reponse-etait', { nom: q.cible.noms.gcf })}

+

+ {q.cible.noms.gcf} : {q.cible.saison_mois?.map((m) => MOIS[m - 1]).join(', ')} +

+

+ + {t(locale, 'zerbaj.voir')} +

+ {/if} +
+ {/if} +
+ +