Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d1eba9e35 | ||
|
|
ac27625e67 | ||
|
|
73f4cfce51 | ||
|
|
351482fa9e | ||
|
|
5d0f7a9616 | ||
|
|
ed6d0a7140 | ||
|
|
217c40a245 |
@@ -41,7 +41,9 @@ export const HOME_TEXTS = {
|
||||
rulesContinue: (step: number) => `Reprendre à l'étape ${step}`,
|
||||
rulesAgain: "Revoir",
|
||||
progress: (played: number, won: number) =>
|
||||
`${plural(played, "partie jouée", "parties jouées")} · ${plural(won, "gagnée", "gagnées")}`,
|
||||
played === 0
|
||||
? "aucune partie jouée"
|
||||
: `${plural(played, "partie jouée", "parties jouées")} · ${plural(won, "gagnée", "gagnées")}`,
|
||||
footer: "Né à l'Atelier du Huit, à Cugnaux. Logiciel libre, sans publicité ni mise.",
|
||||
|
||||
// Installation
|
||||
|
||||
@@ -290,10 +290,11 @@ export function createHomeView(options: HomeViewOptions): HomeView {
|
||||
if (state.lessons !== null) {
|
||||
parts.push(LESSON_TEXTS.homeSummary(state.lessons.finished, state.lessons.total));
|
||||
}
|
||||
if (state.progress !== null && state.progress.played > 0) {
|
||||
if (state.progress !== null) {
|
||||
parts.push(HOME_TEXTS.progress(state.progress.played, state.progress.won));
|
||||
}
|
||||
progress.textContent = parts.join(" · ");
|
||||
const line = parts.join(" · ");
|
||||
progress.textContent = line.charAt(0).toUpperCase() + line.slice(1);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -53,5 +53,7 @@ export const LESSON_TEXTS = {
|
||||
homeAllDoneDetail: "Vous pouvez les revoir quand vous voulez.",
|
||||
allLessons: "Toutes les leçons",
|
||||
homeSummary: (finished: number, total: number) =>
|
||||
`${plural(finished, "leçon terminée", "leçons terminées")} sur ${total}`,
|
||||
finished === 0
|
||||
? `Aucune leçon terminée sur ${total}`
|
||||
: `${plural(finished, "leçon terminée", "leçons terminées")} sur ${total}`,
|
||||
} as const;
|
||||
|
||||
@@ -86,9 +86,10 @@ export function createLessonsView(section: HTMLElement): LessonsView {
|
||||
const head = element(section, "div", { className: "lessons-intro" });
|
||||
element(head, "h2", { text: LESSON_TEXTS.listTitle });
|
||||
element(head, "p", { className: "muted", text: LESSON_TEXTS.listLead });
|
||||
const rules = element(section, "p", { className: "lessons-rules caption" });
|
||||
rules.append(`${LESSON_TEXTS.rulesReminder} · `);
|
||||
const review = element(rules, "a", { id: "lessons-rules", text: LESSON_TEXTS.rulesReview });
|
||||
// Le rappel des règles : toute la ligne est le lien, haute de 44 px.
|
||||
const review = element(section, "a", { id: "lessons-rules", className: "lessons-rules" });
|
||||
review.append(`${LESSON_TEXTS.rulesReminder} · `);
|
||||
element(review, "span", { text: LESSON_TEXTS.rulesReview });
|
||||
review.href = "#/tutoriel/1";
|
||||
review.setAttribute("aria-label", LESSON_TEXTS.rulesReviewLabel);
|
||||
const status = element(section, "p", { id: "lessons-status", className: "muted" });
|
||||
|
||||
@@ -287,14 +287,16 @@ export function createTutorialView(options: TutorialViewOptions): TutorialView {
|
||||
skip.textContent = course.texts.quit;
|
||||
skip.setAttribute("aria-label", course.texts.quitLabel);
|
||||
const overline = element(section, "p", { id: idOf("overline"), className: "overline" });
|
||||
const title = element(section, "h2", { id: idOf("title"), className: "tutorial-title" });
|
||||
// Le titre, et « Revoir » à sa droite (en portrait comme en paysage).
|
||||
const titleRow = element(section, "div", { className: "tutorial-title-row" });
|
||||
const title = element(titleRow, "h2", { id: idOf("title"), className: "tutorial-title" });
|
||||
const boardBox = element(section, "div", { className: "tutorial-board-box" });
|
||||
const svg = document.createElementNS(SVG_NS, "svg");
|
||||
svg.id = idOf("board");
|
||||
svg.setAttribute("class", "board tutorial-board");
|
||||
svg.setAttribute("role", "img");
|
||||
boardBox.append(svg);
|
||||
const replay = element(boardBox, "button", {
|
||||
const replay = element(titleRow, "button", {
|
||||
id: idOf("replay"),
|
||||
className: "icon-button tutorial-replay",
|
||||
});
|
||||
|
||||
@@ -106,7 +106,7 @@ test("cinq leçons, de deux à quatre exercices chacune, avec un titre et une fi
|
||||
lessons.map((l) => l.title),
|
||||
[
|
||||
"Les ouvertures",
|
||||
"Construire ses cases",
|
||||
"Faire des points",
|
||||
"Frapper ou ne pas frapper",
|
||||
"La course et le compte de points",
|
||||
"Sortir sans se faire frapper",
|
||||
@@ -217,7 +217,9 @@ test("chaque coup attendu est le meilleur coup du moteur à 2 plis, avec plus de
|
||||
assert.equal(report.length, 16);
|
||||
});
|
||||
|
||||
test("chaque coup illustré est le meilleur coup du moteur à 2 plis", async () => {
|
||||
// Couvre aussi le « nettement devant tous les autres » de la leçon 1 (étape
|
||||
// 1) : chaque coup illustré a plus de MIN_GAP d'avance sur le deuxième.
|
||||
test("chaque coup illustré est le meilleur coup du moteur à 2 plis, avec plus de 0,04 d'avance", async () => {
|
||||
const engine = await loadNodeEngine();
|
||||
let illustrated = 0;
|
||||
for (const [name, screen] of allScreens()) {
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ Repris du cadrage (modules, écrans clés, IA générative en V1) et des ADR 001
|
||||
| 1g. Accueil, tutoriel et leçons (fait) | Accueil (reprendre, nouvelle partie, prochaine leçon, progression résumée), tutoriel des règles interactif et illustré (0e.5), cinq premières leçons numérotées (ADR 0011) avec positions et « à vous de jouer », progression locale ; contenus CC BY-SA 4.0 livrés sans relecture préalable, sous les garde-fous du moteur (décision du 2026-11-09) | 0e.5, 1a |
|
||||
| 1h. Progression | Historique des parties et courbe du taux d'erreur | 1b, 1e |
|
||||
| 1i. Plateau soigné | Dessin du plateau aligné sur la charte (`docs/identite/charte/board.js`), animations des pions, glisser-déposer en plus du toucher, double toucher pour le seul coup évident | 0e.3 |
|
||||
| 1j. Plan de test | `docs/guide-test-etape-1.md` : parcours du débutant et du joueur de club sur le téléphone, avec ce qu'il faut rapporter | toutes |
|
||||
| 1j. Plan de test (fait) | `docs/guide-test-etape-1.md` : parcours du débutant et du joueur de club sur le téléphone, avec ce qu'il faut rapporter | toutes |
|
||||
|
||||
## Orientation donnée par Pierre
|
||||
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
# Guide de test de l'étape 1 : « Jouer et comprendre »
|
||||
|
||||
Document temporaire : il sert à la vérification de l'étape 1 avec Pierre, sur
|
||||
le Fairphone 4, et sera supprimé à la clôture de l'étape (ce qui en reste de
|
||||
durable va dans le journal).
|
||||
|
||||
**Critère de fin de l'étape** (cadrage) : un débutant apprend et joue seul ; un
|
||||
joueur de club comprend ses erreurs.
|
||||
|
||||
Le guide suit deux parcours : celui du **débutant** (parties A à C) et celui du
|
||||
**joueur de club** (parties D à F). Une partie G regroupe ce qui est commun
|
||||
(installation, hors ligne, mise à jour, affichage). Comptez environ une heure
|
||||
pour l'ensemble, en plusieurs fois si besoin : l'application reprend là où
|
||||
vous en étiez.
|
||||
|
||||
Pour chaque point : cochez ce qui marche, et notez ce qui coince avec le
|
||||
numéro du point (par exemple « C4 : le bilan s'ouvre deux fois »). Une capture
|
||||
d'écran aide beaucoup.
|
||||
|
||||
## Avant de commencer
|
||||
|
||||
- Adresse : https://ustagammon.sans.pub, dans Chrome sur le Fairphone 4.
|
||||
- Version : le menu, puis « Aide et glossaire », onglet « À propos », donne le
|
||||
numéro de version (un commit). Notez-le en tête de vos retours.
|
||||
- Pour repartir de zéro (comme un nouveau joueur) : dans Chrome, Paramètres du
|
||||
site pour ustagammon.sans.pub, puis « Effacer et réinitialiser ». Toutes les
|
||||
parties et la progression sont alors perdues.
|
||||
|
||||
## A. Premier contact (débutant)
|
||||
|
||||
1. Ouvrez l'application après l'avoir réinitialisée. L'écran de premier
|
||||
lancement propose « Je découvre » et « Je connais les règles ».
|
||||
2. Touchez « Je découvre » : le tutoriel des règles s'ouvre à l'étape 1 sur 8.
|
||||
3. Suivez les huit étapes jusqu'au bout. À chaque écran « à vous » : jouez
|
||||
d'abord un mauvais coup (le pion revient, une phrase explique sans
|
||||
reproche), puis le bon (coche, « Continuer »).
|
||||
4. Étape 2 : deux flèches circulaires montrent le sens de chaque camp, sous
|
||||
les pions.
|
||||
5. Le texte est-il clair pour quelqu'un qui n'a jamais joué ? Notez les mots
|
||||
ou phrases qui bloquent. Les mots soulignés ouvrent leur fiche du glossaire.
|
||||
6. À la fin, l'accueil propose la suite (partie, leçons).
|
||||
|
||||
**À rapporter** : une étape incompréhensible, un coup juste refusé ou un coup
|
||||
faux accepté, un texte coupé ou qui déborde.
|
||||
|
||||
## B. Première partie (débutant)
|
||||
|
||||
1. Depuis l'accueil, « Nouvelle partie » : choisissez « Partie libre » et le
|
||||
niveau « Débutant », conseil du maître activé, puis « Commencer la partie ».
|
||||
2. Touchez les dés pour les lancer. Touchez un pion : s'il n'a qu'une case
|
||||
d'arrivée, il y va tout seul ; sinon, ses cases d'arrivée s'allument.
|
||||
3. Essayez aussi de glisser un pion avec le doigt jusqu'à sa case ; lâché
|
||||
ailleurs, il revient à sa place.
|
||||
4. Un double toucher sur un pion joue le coup évident s'il n'y en a qu'un.
|
||||
5. Avant de valider, « Annuler » reprend le dernier pion, un à la fois. Puis
|
||||
validez en touchant les dés ou « Jouer ce coup ».
|
||||
6. Après votre coup, une ligne discrète du maître apparaît (« Bon coup. »,
|
||||
« Erreur. J'aurais joué… »). Touchez-la : la feuille montre votre coup et
|
||||
celui du maître, joués pion par pion.
|
||||
7. Regardez le coup de l'ordinateur : ses pions glissent, des traces restent,
|
||||
« Revoir » le rejoue. Touchez la notation du coup : la fiche « Notation des
|
||||
coups » s'ouvre.
|
||||
8. Quand aucun coup n'est possible, les dés sont barrés : touchez-les pour
|
||||
passer.
|
||||
9. Jouez la partie jusqu'au bout.
|
||||
|
||||
**À rapporter** : un pion qui ne bouge pas comme prévu, un geste raté, une
|
||||
phrase du maître fausse ou blessante, la durée d'un tour de l'ordinateur si
|
||||
elle dépasse 2 s.
|
||||
|
||||
## C. Bilan, leçons et retour (débutant)
|
||||
|
||||
1. En fin de partie, le bilan s'affiche : l'issue, ce qui a fait la
|
||||
différence, vos décisions comptées, votre jeu et vos dés, le coup à revoir.
|
||||
2. « Rejouer » relance une partie avec les mêmes réglages ; « Changer les
|
||||
réglages » ouvre l'écran « Nouvelle partie ».
|
||||
3. Depuis l'accueil, ouvrez « Toutes les leçons » puis la leçon 1. Suivez-la,
|
||||
en faisant au moins un exercice faux puis juste.
|
||||
4. Revenez à la liste : la leçon affiche « k exercices sur n ». Fermez
|
||||
l'application, rouvrez-la : la progression est gardée.
|
||||
5. Faites les cinq leçons si vous avez le temps : chaque réponse attendue est
|
||||
le meilleur coup du moteur.
|
||||
|
||||
**À rapporter** : une explication de leçon qui vous semble fausse ou trop
|
||||
difficile pour un débutant, un exercice où le refus ne met pas sur la voie.
|
||||
|
||||
## D. Match et niveaux (joueur de club)
|
||||
|
||||
1. « Nouvelle partie » : choisissez « Match », 3 points, niveau « Avancé »,
|
||||
puis « Commencer le match ».
|
||||
2. Le score du match s'affiche (« Match en 3 points ») ; à 2 points d'un
|
||||
joueur, la partie Crawford est signalée et le videau disparaît.
|
||||
3. Doublez au moins une fois (le videau demande confirmation). Répondez au
|
||||
moins une fois à un double de l'ordinateur (Prendre ou Passer).
|
||||
4. Entre deux parties, « Partie suivante » ; à la fin du match, le score final.
|
||||
5. Le niveau de l'ordinateur paraît-il juste ? « Champion » est le plus fort.
|
||||
|
||||
**À rapporter** : une décision de videau de l'ordinateur qui vous surprend
|
||||
(notez la position avec « Copier le lien de la position » dans le menu), un
|
||||
score de match faux.
|
||||
|
||||
## E. Comprendre ses erreurs (joueur de club)
|
||||
|
||||
1. Après une partie, depuis le bilan, « Voir l'analyse ».
|
||||
2. La courbe de vos chances de gagner se parcourt du doigt ; toucher un point
|
||||
ouvre le coup.
|
||||
3. Le visualiseur montre « Votre coup » et « Le coup du maître ». Les chiffres
|
||||
du moteur sont repliés en dessous.
|
||||
4. Filtrez la liste des coups par appréciation (erreurs, grosses erreurs).
|
||||
5. Sur un coup, « Rejouer d'ici » : une branche part de ce coup, la partie
|
||||
d'origine reste dans « Mes parties ».
|
||||
6. Sur une erreur, « Demander à une IA » : le prompt s'affiche, « Copier le
|
||||
prompt » confirme par « ✓ Copié ». Collez-le dans l'IA de votre choix et
|
||||
jugez la réponse.
|
||||
7. Ouvrez la progression (la ligne de l'accueil) après trois parties : la
|
||||
courbe du taux d'erreur, la liste des parties, « Créer des exercices ».
|
||||
8. « Mes parties » (menu) : reprenez une ancienne partie, exportez-en une en
|
||||
SGF et ouvrez-la dans GNU Backgammon de bureau (`load game fichier.sgf`).
|
||||
|
||||
**À rapporter** : une analyse qui contredit ce que vous pensiez (c'est peut-être
|
||||
juste, mais notez-la), un prompt incomplet, une partie absente de la liste.
|
||||
|
||||
## F. Liens et positions (joueur de club)
|
||||
|
||||
1. Dans le menu, « Copier le lien de la position ». Ouvrez ce lien dans un
|
||||
nouvel onglet : la même position s'affiche, avec « Jouer à partir d'ici ».
|
||||
2. L'aide, une question de l'aide, un terme du glossaire, la progression : chaque
|
||||
écran a son adresse ; le bouton retour du téléphone ferme d'abord la feuille
|
||||
ouverte, puis revient à l'écran précédent.
|
||||
|
||||
## G. Commun : installation, hors ligne, affichage
|
||||
|
||||
1. Après une partie terminée, l'accueil propose « Installer l'application » :
|
||||
installez-la, puis ouvrez-la depuis l'écran d'accueil du téléphone.
|
||||
2. Mode avion : l'application s'ouvre et se joue normalement.
|
||||
3. Tournez le téléphone : en paysage, le plateau prend l'écran, la partie
|
||||
continue sans perte. Essayez « Plein écran » dans le menu.
|
||||
4. Menu, thème clair puis sombre : les pions blancs et noirs se distinguent
|
||||
bien partout, y compris sur la barre.
|
||||
5. Vibrations : pion posé, pion frappé, double de l'ordinateur, fin de partie.
|
||||
Coupez-les dans le menu.
|
||||
6. Mise à jour : à la prochaine publication, le bandeau « Nouvelle version
|
||||
prête » apparaît en bas ; « Mettre à jour » vous ramène à votre partie.
|
||||
7. Performances : notez si l'application ralentit, chauffe ou se recharge
|
||||
seule (elle fait tourner deux moteurs : un pour jouer, un pour analyser).
|
||||
|
||||
## Ce qu'il faut rapporter
|
||||
|
||||
- Le numéro de version (onglet « À propos »).
|
||||
- Pour chaque problème : le numéro du point, ce que vous avez fait, ce que vous
|
||||
attendiez, ce qui s'est passé, et si possible une capture ou le lien de la
|
||||
position.
|
||||
- Votre avis sur les deux critères : un débutant peut-il apprendre et jouer
|
||||
seul ? Un joueur de club comprend-il ses erreurs ?
|
||||
@@ -236,8 +236,18 @@ l'ordre, chacune avec une pastille ronde à son numéro :
|
||||
|
||||
Toutes les leçons restent ouvertes : l'ordre est conseillé, jamais imposé.
|
||||
Les écrans d'une leçon suivent la forme du tutoriel (barre de progression,
|
||||
« Passer », « Retour », « Continuer », écrans « à vous » contrôlés par le
|
||||
moteur).
|
||||
« Quitter », « Retour », « Continuer », écrans « à vous » contrôlés par le
|
||||
moteur), aux adresses `#/lecons/<n>/<écran>`, `…/a-vous` et `…/fin`.
|
||||
|
||||
- Une leçon est **terminée** quand tous ses exercices sont réussis.
|
||||
- « Retour » et « Continuer » restent **en bas de l'écran**, sous le pouce,
|
||||
comme dans le tutoriel, quelle que soit la longueur du texte.
|
||||
- « Revoir » (rejouer le coup illustré) se place sur la ligne du titre, à
|
||||
droite, en portrait comme en paysage : le texte garde toute la largeur.
|
||||
- En paysage, la colonne de droite garde la ligne `caption` « Leçon 1 · 1 sur
|
||||
4 · À vous » au-dessus du titre.
|
||||
- Les titres et les textes n'emploient que les mots du glossaire : « point »
|
||||
et « flèche », jamais « case ».
|
||||
|
||||
## 1h. Progression (`#/progression`)
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ Vous avez 4 et 2. Faites un point dans votre jan intérieur.
|
||||
|
||||
- position: 4HPwATDgc/ABMA
|
||||
|
||||
La flèche 7 ouvre votre [jan extérieur](jan-exterieur.md), juste à côté de la barre. Avec la flèche 6, déjà fermée par vos cinq pions, elle forme deux points côte à côte.
|
||||
La flèche 7 ouvre votre [jan extérieur](jan-exterieur.md), juste à côté de la barre. Fermée, elle formerait avec la flèche 6, où sont déjà vos cinq pions, deux points côte à côte.
|
||||
|
||||
Les deux pions noirs de votre flèche 1 doivent passer par là : depuis la flèche 1, un 5 les mène sur la flèche 6 et un 6 sur la flèche 7.
|
||||
Les deux pions noirs de votre flèche 1 doivent passer par là : depuis la flèche 1, un 6 les mène sur la flèche 7, et un 5 est déjà bloqué par votre point 6.
|
||||
|
||||
### À vous
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Construire ses cases
|
||||
# Faire des points
|
||||
|
||||
Leçon 2 d'Ustagammon : construire ses cases et faire des points. Texte sous
|
||||
Leçon 2 d'Ustagammon : construire son jeu en faisant des points. Texte sous
|
||||
licence CC BY-SA 4.0. Format : voir README.md.
|
||||
|
||||
## 1. Deux pions font un point
|
||||
@@ -27,7 +27,7 @@ Même position, vous avez 6 et 2. Votre pion de la flèche 5 est seul : couvrez-
|
||||
|
||||
- position: 4HPwATDQZ+EBMA
|
||||
|
||||
Un pion posé sur les flèches 7 à 11, dans votre [jan extérieur](jan-exterieur.md), peut entrer dans votre jan intérieur avec un seul dé.
|
||||
Un pion posé sur les flèches 7 à 12, dans votre [jan extérieur](jan-exterieur.md), peut entrer dans votre jan intérieur avec un seul dé.
|
||||
|
||||
Ici, un pion blanc est seul sur la flèche 5, un autre sur la flèche 9.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ sous licence CC BY-SA 4.0. Format : voir README.md.
|
||||
|
||||
Un pion noir est seul sur votre flèche 7 : c'est un [blot](blot.md). Avec 6 et 1, je joue 13/7* 8/7. L'astérisque marque le pion frappé : il part sur la [barre](barre.md).
|
||||
|
||||
Mes deux pions restent ensemble sur la flèche 7 : le point est fait, aucun pion noir ne peut plus s'y poser.
|
||||
Les deux pions blancs restent ensemble sur la flèche 7 : le point est fait, aucun pion noir ne peut plus s'y poser.
|
||||
|
||||
### À vous
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ Vous avez 5 et 2.
|
||||
|
||||
Ici, les pions de l'ordinateur ont 81 flèches à parcourir, les vôtres 110 : il a 29 points d'avance. Son dernier pion est seul sur votre flèche 9.
|
||||
|
||||
Frappé, ce pion part sur la barre, et le compte de l'ordinateur remonte : de la flèche 16 de son côté jusqu'à la barre, 9 de plus.
|
||||
Frappé, ce pion part sur la barre : pour l'ordinateur, il comptait 16 et compte maintenant 25, soit 9 de plus.
|
||||
|
||||
### À vous
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# language: fr
|
||||
Fonctionnalité: Progresser avec les leçons
|
||||
|
||||
Cinq leçons courtes, sur le vrai plateau : les ouvertures, construire ses
|
||||
cases, frapper ou non, la course et le compte de points, sortir sans se
|
||||
Cinq leçons courtes, sur le vrai plateau : les ouvertures, faire des
|
||||
points, frapper ou non, la course et le compte de points, sortir sans se
|
||||
faire frapper. Chacune explique une idée en quelques écrans, puis vous
|
||||
fait jouer le coup du maître. GNU Backgammon vérifie chaque coup.
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
||||
import { chromium, firefox } from "playwright";
|
||||
import { hiddenUnderActionBar, tutorialWidthProblems, tutorialWidths } from "./recette.mjs";
|
||||
import { hiddenUnderActionBar, missingTopbar, tutorialWidthProblems, tutorialWidths } from "./recette.mjs";
|
||||
|
||||
const [base, browserName, shots, updateBase, updateDir] = process.argv.slice(2);
|
||||
const browserType = { chromium, firefox }[browserName];
|
||||
@@ -117,6 +117,8 @@ async function capture(page, name, settle = async () => {}, sizes = SIZES) {
|
||||
await settle();
|
||||
const hidden = await hiddenUnderActionBar(page);
|
||||
check(hidden.length === 0, `${name} ${width}x${height} : caché sous la barre d'action : ${hidden.join(", ")}`);
|
||||
const topbar = await missingTopbar(page);
|
||||
check(topbar === null, `${name} : ${topbar}`);
|
||||
await page.screenshot({ path: `${dir}/${name}-${width}x${height}.png`, fullPage: true });
|
||||
captures++;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
import { mkdirSync } from "node:fs";
|
||||
import { chromium, firefox } from "playwright";
|
||||
import { hiddenUnderActionBar, tallButtons } from "./recette.mjs";
|
||||
import { hiddenUnderActionBar, missingTopbar, tallButtons } from "./recette.mjs";
|
||||
|
||||
const [base, browserName, shots] = process.argv.slice(2);
|
||||
const browserType = { chromium, firefox }[browserName];
|
||||
@@ -68,6 +68,8 @@ async function capture(page, name, settle = async () => {}) {
|
||||
await settle();
|
||||
const hidden = await hiddenUnderActionBar(page);
|
||||
check(hidden.length === 0, `${name} ${width}x${height} : caché sous la barre d'action : ${hidden.join(", ")}`);
|
||||
const topbar = await missingTopbar(page);
|
||||
check(topbar === null, `${name} : ${topbar}`);
|
||||
const tall = await tallButtons(page);
|
||||
check(tall.length === 0, `${name} ${width}x${height} : boutons étirés : ${tall.join(", ")}`);
|
||||
await page.screenshot({ path: `${dir}/${name}-${width}x${height}.png`, fullPage: true });
|
||||
@@ -155,7 +157,8 @@ try {
|
||||
await until(page, "carte de la leçon", () => {
|
||||
const card = document.getElementById("home-lesson");
|
||||
return location.hash === "#/accueil" && !card.hidden && !card.closest("[hidden]") &&
|
||||
card.textContent.includes("Leçon 1") && !document.getElementById("home-new").disabled;
|
||||
card.textContent.includes("Leçon 1") && !document.getElementById("home-new").disabled &&
|
||||
document.getElementById("home-progress").textContent.includes(" · ");
|
||||
});
|
||||
const home = await page.evaluate(() => ({
|
||||
go: document.getElementById("home-lesson-go").getAttribute("href"),
|
||||
@@ -170,7 +173,7 @@ try {
|
||||
}));
|
||||
check(home.go === "#/lecons/1/1" && home.goText === "Commencer" && home.goClass.includes("secondary") &&
|
||||
home.all === "#/lecons" && home.title === "Les ouvertures" && home.state === "À commencer · 4 exercices" &&
|
||||
home.summary.startsWith("0 leçon terminée sur 5") && home.primaries === 1,
|
||||
home.summary.startsWith("Aucune leçon terminée sur 5 · ") && home.primaries === 1,
|
||||
`carte de l'accueil : ${JSON.stringify(home)}`);
|
||||
await capture(page, "01-accueil-carte", async () => {
|
||||
await page.locator("#home-lesson").scrollIntoViewIfNeeded();
|
||||
@@ -188,6 +191,8 @@ try {
|
||||
[...document.querySelectorAll("#lessons a.button:not(.secondary)")].filter((el) => !el.closest("[hidden]")).length);
|
||||
check(primaries === 1, `liste : ${primaries} actions principales`);
|
||||
check(await page.getAttribute("#lessons-rules", "href") === "#/tutoriel/1", "liste : rappel des règles");
|
||||
const rulesHeight = await page.evaluate(() => document.getElementById("lessons-rules").getBoundingClientRect().height);
|
||||
check(rulesHeight >= 44, `liste : rappel des règles haut de ${rulesHeight} px`);
|
||||
await capture(page, "02-liste");
|
||||
log("liste : cinq leçons, la première proposée, une seule action principale, rappel des règles");
|
||||
|
||||
@@ -208,6 +213,18 @@ try {
|
||||
explain.segments === 4 && explain.skip === "#/lecons" && explain.back === "#/lecons" &&
|
||||
explain.next === "#/lecons/1/1/a-vous" && explain.topbar === "none" &&
|
||||
explain.glossary.includes("#/glossaire/jan-interieur"), `explication : ${JSON.stringify(explain)}`);
|
||||
// « Revoir » sur la ligne du titre ; « Retour » et « Continuer » en bas
|
||||
// de l'écran, sous le pouce (recette 1g).
|
||||
const layout = await page.evaluate(() => {
|
||||
const title = document.getElementById("lesson-title").getBoundingClientRect();
|
||||
const replay = document.getElementById("lesson-replay").getBoundingClientRect();
|
||||
const next = document.getElementById("lesson-next").getBoundingClientRect();
|
||||
return { titleTop: title.top, titleBottom: title.bottom, replayTop: replay.top,
|
||||
replayBottom: replay.bottom, nextBottom: next.bottom, height: innerHeight };
|
||||
});
|
||||
check(layout.replayTop < layout.titleBottom && layout.replayBottom > layout.titleTop,
|
||||
`« Revoir » hors de la ligne du titre : ${JSON.stringify(layout)}`);
|
||||
check(layout.nextBottom > layout.height - 64, `« Continuer » loin du bas : ${JSON.stringify(layout)}`);
|
||||
check(await page.locator("#lesson-board .trace-arrow").count() > 0, "le coup illustré n'a pas laissé de traces");
|
||||
await capture(page, "03-explication");
|
||||
log("explication : coup illustré 8/5 6/5 joué tout seul, barre de quatre étapes, liens du glossaire");
|
||||
|
||||
@@ -99,6 +99,27 @@ export async function hiddenUnderActionBar(target) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* La barre haute (Aide, Plein écran, Menu) hors de l'écran de jeu : sur une
|
||||
* page (accueil, leçons, aide…) qui n'a pas son propre en-tête, elle doit
|
||||
* rester visible, en paysage comme en portrait ; sans elle, le menu est
|
||||
* inaccessible (recette 1g). Renvoie un problème décrit, ou null.
|
||||
*/
|
||||
export async function missingTopbar(target) {
|
||||
return target.evaluate(() => {
|
||||
const root = document.documentElement;
|
||||
if (!root.classList.contains("page-open") || root.classList.contains("bare")) {
|
||||
return null;
|
||||
}
|
||||
const bar = document.querySelector(".topbar");
|
||||
const r = bar?.getBoundingClientRect();
|
||||
const shown = bar !== null && getComputedStyle(bar).display !== "none" && r.height > 0;
|
||||
return shown
|
||||
? null
|
||||
: `barre haute absente sur ${root.dataset.screen} (${innerWidth}x${innerHeight})`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Les largeurs d'un écran du tutoriel, en px CSS : le plateau, la colonne
|
||||
* de texte, et la largeur de la page (scrollWidth) face à celle de l'écran.
|
||||
|
||||
+73
-25
@@ -1201,6 +1201,13 @@ html.landscape.page-open body {
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
/* Hors de l'écran de jeu, la barre haute (Aide, Plein écran, Menu) reste
|
||||
* en paysage : seul le plateau en paysage s'en passe. Les écrans qui ont
|
||||
* leur propre en-tête (bare) la masquent toujours. */
|
||||
html.landscape.page-open:not(.bare) .topbar {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Repli du plein écran : le paysage ne peut pas être verrouillé, l'écran
|
||||
* entier tourne d'un quart de tour dans le portrait. Les touchers suivent
|
||||
* la rotation ; le plateau et les fenêtres sont posés dans #screen. */
|
||||
@@ -1876,17 +1883,20 @@ html.bare .topbar {
|
||||
.lessons-rules {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
min-height: 44px;
|
||||
margin-top: var(--space-2);
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
color: var(--muted);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.lessons-rules a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 44px;
|
||||
.lessons-rules span {
|
||||
margin-left: var(--space-1);
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#lessons-status:empty {
|
||||
@@ -2136,8 +2146,28 @@ html.bare .topbar {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.tutorial-title {
|
||||
/* En portrait, l'écran tient au moins la hauteur de l'écran : « Retour »
|
||||
* et « Continuer » restent en bas, sous le pouce. */
|
||||
.tutorial {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
}
|
||||
|
||||
/* Le titre, et « Revoir » sur sa ligne, à droite. */
|
||||
.tutorial-title-row {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
align-items: center;
|
||||
margin: var(--space-1) 0 var(--space-3);
|
||||
}
|
||||
|
||||
.tutorial-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
line-height: 30px;
|
||||
}
|
||||
@@ -2152,16 +2182,13 @@ html.bare .topbar {
|
||||
}
|
||||
}
|
||||
|
||||
.tutorial-board-box .tutorial-replay {
|
||||
position: absolute;
|
||||
right: var(--space-1);
|
||||
bottom: calc(-44px - var(--space-1));
|
||||
.tutorial-replay {
|
||||
flex: none;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.tutorial-text {
|
||||
margin-top: var(--space-3);
|
||||
padding-right: 52px;
|
||||
}
|
||||
|
||||
.tutorial-text p {
|
||||
@@ -2214,7 +2241,8 @@ html.bare .topbar {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.4fr;
|
||||
gap: var(--space-2);
|
||||
margin-top: var(--space-6);
|
||||
margin-top: auto;
|
||||
padding-top: var(--space-6);
|
||||
}
|
||||
|
||||
.tutorial-nav .button {
|
||||
@@ -2281,16 +2309,46 @@ html.landscape .tutorial-head {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
html.landscape .tutorial-title {
|
||||
html.landscape .tutorial-title-row {
|
||||
grid-row: 2;
|
||||
margin: 0 0 var(--space-1);
|
||||
padding-right: calc(44px + var(--space-4));
|
||||
padding-right: calc(var(--space-2) + env(safe-area-inset-right));
|
||||
}
|
||||
|
||||
html.landscape .tutorial-title {
|
||||
font-size: 20px;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
/* La boîte du plateau s'efface : le plateau et « Revoir » se placent
|
||||
* eux-mêmes dans la grille. */
|
||||
/* Une leçon garde en paysage sa ligne « Leçon 1 · 1 sur 4 · à vous »
|
||||
* au-dessus du titre (recette 1g) : une rangée de plus dans la grille. */
|
||||
html.landscape #lesson {
|
||||
grid-template-rows: auto auto auto minmax(0, 1fr) auto auto;
|
||||
}
|
||||
|
||||
html.landscape #lesson .overline {
|
||||
display: block;
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
html.landscape #lesson .tutorial-title-row {
|
||||
grid-row: 3;
|
||||
}
|
||||
|
||||
html.landscape #lesson .tutorial-body {
|
||||
grid-row: 4;
|
||||
}
|
||||
|
||||
html.landscape #lesson .tutorial-cube {
|
||||
grid-row: 5;
|
||||
}
|
||||
|
||||
html.landscape #lesson .tutorial-nav {
|
||||
grid-row: 6;
|
||||
}
|
||||
|
||||
/* La boîte du plateau s'efface : le plateau se place lui-même dans la
|
||||
* grille. */
|
||||
html.landscape .tutorial-board-box {
|
||||
display: contents;
|
||||
}
|
||||
@@ -2305,16 +2363,6 @@ html.landscape .tutorial-board {
|
||||
background: var(--frame);
|
||||
}
|
||||
|
||||
html.landscape .tutorial-board-box .tutorial-replay {
|
||||
position: static;
|
||||
grid-row: 2;
|
||||
grid-column: 2;
|
||||
justify-self: end;
|
||||
align-self: start;
|
||||
margin: -9px calc(var(--space-2) + env(safe-area-inset-right)) 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html.landscape .tutorial-body {
|
||||
grid-row: 3;
|
||||
min-height: 0;
|
||||
|
||||
Reference in New Issue
Block a user