diff --git a/app/src/ui/board-view.ts b/app/src/ui/board-view.ts index 35bf99a..f2e9dd6 100644 --- a/app/src/ui/board-view.ts +++ b/app/src/ui/board-view.ts @@ -303,23 +303,17 @@ function drawPoint( "data-point": number, points: `${left},${base} ${left + g.pointWidth},${base} ${center},${tip}`, }); - // Numéros : sur le cadre en paysage, dans la bande centrale en - // portrait. Leur taille (12 px au moins à l'écran) dépend de l'échelle + // Numéros : sur le cadre, bandes haute et basse, en portrait comme en + // paysage. Leur taille (12 px au moins à l'écran) dépend de l'échelle // du dessin : c'est une mesure du dessin, comme x et y. const size = g.numberSize; - let y: number; - if (g.layout === "landscape") { - y = top ? g.edge / 2 + size * 0.36 : g.height - g.edge / 2 + size * 0.36; - } else { - y = top ? middle - g.die / 2 - 4 : middle + g.die / 2 + 4 + size * 0.72; - } add( svg, "text", { - class: g.layout === "landscape" ? "point-number on-frame" : "point-number", + class: "point-number", x: center, - y, + y: top ? g.edge / 2 + size * 0.36 : g.height - g.edge / 2 + size * 0.36, "font-size": size, }, String(number), diff --git a/app/src/ui/geometry.ts b/app/src/ui/geometry.ts index bcacc61..6620af0 100644 --- a/app/src/ui/geometry.ts +++ b/app/src/ui/geometry.ts @@ -25,7 +25,7 @@ export interface Geometry { readonly height: number; /** Épaisseur du cadre sur les côtés. */ readonly frame: number; - /** Hauteur du cadre en haut et en bas : la bande des numéros en paysage. */ + /** Hauteur du cadre en haut et en bas : la bande des numéros des flèches. */ readonly edge: number; /** Colonne de commandes à gauche (0 en portrait). */ readonly left: number; @@ -95,14 +95,19 @@ export function boardGeometry(layout: Layout, aspect: number, pxPerUnit: number) arrowWidth: 2.5 / pxPerUnit, }; if (layout === "portrait") { - // Les mesures du plateau d'origine : flèches de 44, pions de 20. + // Les mesures du plateau d'origine : flèches de 44. Les bandes haute + // et basse du cadre portent les numéros des flèches, comme en + // paysage (12 px au moins à l'écran) : pions de 19 au plus. La bande ne + // dépasse pas 36 unités : sur une miniature (moins de 275 px de + // large), les numéros rapetissent plutôt que d'écraser les flèches. const frame = 8; + const edge = Math.min(36, Math.max(24, numberSize + 8)); const tray = 48; const bar = 40; const width = 640; const pointWidth = (width - 3 * frame - tray - bar) / 12; const band = 84; - const radius = Math.min(pointWidth / 2 - 2, (height / 2 - frame - band / 2) / 10); + const radius = Math.min(pointWidth / 2 - 2, (height / 2 - edge - band / 2) / 10); const x0 = frame; const barLeft = x0 + 6 * pointWidth; return { @@ -110,7 +115,7 @@ export function boardGeometry(layout: Layout, aspect: number, pxPerUnit: number) width, height, frame, - edge: frame, + edge, left: 0, tray, bar, @@ -123,7 +128,7 @@ export function boardGeometry(layout: Layout, aspect: number, pxPerUnit: number) rightHalf: barLeft + bar, trayLeft: barLeft + bar + 6 * pointWidth + frame, target, - numberSize, + numberSize: Math.min(numberSize, edge - 8), bandFont: atLeast(13.5, pxPerUnit, 17), chipFont: atLeast(13.5, pxPerUnit, 17), ...sizes, diff --git a/app/tests/layout.test.ts b/app/tests/layout.test.ts index a0eaa20..b2bfdb8 100644 --- a/app/tests/layout.test.ts +++ b/app/tests/layout.test.ts @@ -9,7 +9,7 @@ import assert from "node:assert/strict"; import { readdirSync, readFileSync, statSync } from "node:fs"; import { test } from "node:test"; import { createSettingsStore } from "../src/storage/settings-store.js"; -import { OPENING_BOARDS, pipLead } from "../src/ui/board-model.js"; +import { homeBoard, OPENING_BOARDS, outerBoard, pipLead } from "../src/ui/board-model.js"; import { BOARD_HEIGHT, boardGeometry, MAX_STACK, stackY } from "../src/ui/geometry.js"; import { createHaptics, HAPTIC_PATTERNS } from "../src/ui/haptics.js"; import { ICON_PATHS } from "../src/ui/icons.js"; @@ -37,15 +37,32 @@ const LANDSCAPES: [number, number][] = [ [800, 308], ]; -test("géométrie en portrait : les mesures du plateau d'origine", () => { +test("géométrie en portrait : les mesures du plateau d'origine, numéros sur le cadre", () => { const g = boardGeometry("portrait", 640 / 520, 396 / 640); assert.equal(g.width, 640); assert.equal(g.pointWidth, 44); - assert.equal(g.radius, 20); - assert.equal(g.step, 42); + // Pions d'un peu moins de 19 unités (20 avant les numéros sur le cadre). + assert.ok(g.radius >= 18.5 && g.radius <= 19, `pions de ${g.radius}`); + assert.equal(g.step, 2 * g.radius + 2); assert.equal(g.trayLeft + g.tray + g.frame, 640); - // Numéros d'au moins 12 px à l'écran, à 412 px de large. + // Numéros d'au moins 12 px à l'écran, à 412 px de large, logés dans les + // bandes haute et basse du cadre (retours des premiers joueurs). assert.ok(g.numberSize * (396 / 640) >= 12, `numéros de ${g.numberSize} unités`); + assert.ok(g.numberSize + 8 <= g.edge, `bande de ${g.edge} pour des numéros de ${g.numberSize}`); +}); + +test("géométrie en portrait : numéros d'au moins 12 px sur le cadre dès 275 px de large", () => { + for (const width of [275, 320, 358, 380, 396, 600]) { + const px = width / 640; + const g = boardGeometry("portrait", 640 / 520, px); + assert.ok(g.numberSize * px >= 12, `${width} px : numéros de ${g.numberSize * px} px`); + assert.ok(g.numberSize < g.edge, `${width} px : numéros hors du cadre`); + assert.ok(g.edge <= 36, `${width} px : bande de ${g.edge}`); + } + // Une miniature : la bande reste de 36 unités, les numéros y tiennent. + const thumbnail = boardGeometry("portrait", 640 / 520, 200 / 640); + assert.equal(thumbnail.edge, 36); + assert.ok(thumbnail.numberSize < thumbnail.edge); }); test("géométrie en paysage : le plateau remplit l'écran, flèches de 44 px et pions de 22 px au moins", () => { @@ -93,6 +110,13 @@ test("en paysage, les pions de la barre s'arrêtent avant les pastilles des comp } }); +test("les jans de chaque camp, numérotés pour le joueur, dans le sens de marche", () => { + assert.deepEqual(homeBoard("player").points, [6, 5, 4, 3, 2, 1]); + assert.deepEqual(outerBoard("player").points, [12, 11, 10, 9, 8, 7]); + assert.deepEqual(homeBoard("computer").points, [19, 20, 21, 22, 23, 24]); + assert.deepEqual(outerBoard("computer").points, [13, 14, 15, 16, 17, 18]); +}); + // -------------------------------------------------------------- Vibrations test("vibrations : les motifs de la charte, et rien quand elles sont coupées", () => { diff --git a/docs/identite/charte/recette-ux.md b/docs/identite/charte/recette-ux.md index 791590f..f58ff60 100644 --- a/docs/identite/charte/recette-ux.md +++ b/docs/identite/charte/recette-ux.md @@ -8,8 +8,8 @@ Référence : `interaction.md`, maquettes « Révision UX » du canevas. ## Toutes tailles - [ ] Flèches impaires en `point-a`, paires en `point-b` ; 12 et 13 de couleurs différentes. -- [ ] Numéros des flèches à l'extérieur du plateau, sur le cadre (bande haute et bande basse), en portrait comme en paysage ; jamais dans le fond du plateau entre les deux rangées. -- [ ] Pions de l'ordinateur lisibles partout : contour et filet `checker-computer-edge` (gris clair) visibles sur la barre, sur les flèches sombres et en thème sombre. +- [ ] Numéros des flèches à l'extérieur du plateau, sur le cadre (bande haute et bande basse), en portrait comme en paysage ; jamais dans le fond du plateau entre les deux rangées. (Vérifié par `tests/browser/retours-joueurs.mjs`.) +- [ ] Pions de l'ordinateur lisibles partout : contour et filet `checker-computer-edge` (gris clair) visibles sur la barre, sur les flèches sombres et en thème sombre. Règle automatique : là où sa couleur ne se détache pas de son fond (moins de 3:1), son contour s'en détache à 3:1 au moins (`checkerContrasts` de `tests/browser/recette.mjs`). - [ ] Une seule action principale (brique) visible à la fois. - [ ] Aucun `window.confirm` ni `alert` dans le code de l'app (`grep -rn "confirm(\|alert(" app/src` vide). - [ ] Aucune cible tactile sous 44 × 44 px (boutons, pictogrammes, dés, videau).