Recette : contraste du contour des pions, retours des joueurs dans le navigateur
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
966822084a
commit
8c2043aca5
@@ -167,3 +167,96 @@ export function tutorialWidthProblems(views) {
|
||||
}
|
||||
return problems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Les contrastes des pions dessinés sur les plateaux visibles de la page
|
||||
* (svg.board), mesurés sur le fond qui porte chacun : la barre, une flèche,
|
||||
* le fond du plateau ou la sortie, trouvé au centre du pion (le dernier
|
||||
* dessiné qui le contient). Pour chaque pion : son camp, ce fond, le
|
||||
* contraste de son contour (stroke) et celui de sa couleur (fill) avec ce
|
||||
* fond. Les pions entourés de brique (jouable, sélectionné) sont laissés :
|
||||
* leur contour est celui du pion jouable, pas celui de son camp.
|
||||
*/
|
||||
export async function checkerContrasts(target) {
|
||||
return target.evaluate(() => {
|
||||
const rgb = (color) => (color.match(/[\d.]+/g) ?? []).slice(0, 3).map(Number);
|
||||
const luminance = (color) => {
|
||||
const [r, g, b] = rgb(color).map((v) => {
|
||||
const c = v / 255;
|
||||
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
||||
});
|
||||
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
};
|
||||
const ratio = (a, b) => {
|
||||
const [dark, light] = [luminance(a), luminance(b)].sort((x, y) => x - y);
|
||||
return (light + 0.05) / (dark + 0.05);
|
||||
};
|
||||
const BACKGROUNDS = [
|
||||
["board-bar", "barre"],
|
||||
["board-tray", "sortie"],
|
||||
["point-a", "flèche impaire"],
|
||||
["point-b", "flèche paire"],
|
||||
["board-bed", "fond"],
|
||||
];
|
||||
const selector = BACKGROUNDS.map(([c]) => `.${c}`).join(", ");
|
||||
const found = [];
|
||||
for (const svg of document.querySelectorAll("svg.board")) {
|
||||
if (svg.closest("[hidden]") !== null || svg.getClientRects().length === 0) {
|
||||
continue;
|
||||
}
|
||||
const shapes = [...svg.querySelectorAll(selector)].reverse();
|
||||
for (const checker of svg.querySelectorAll("circle.checker")) {
|
||||
if (checker.matches(".movable, .selected")) {
|
||||
continue;
|
||||
}
|
||||
const point = svg.createSVGPoint();
|
||||
point.x = Number(checker.getAttribute("cx"));
|
||||
point.y = Number(checker.getAttribute("cy"));
|
||||
const under = shapes.find((s) => s.isPointInFill(point));
|
||||
if (under === undefined) {
|
||||
continue;
|
||||
}
|
||||
const ground = getComputedStyle(under).fill;
|
||||
const style = getComputedStyle(checker);
|
||||
found.push({
|
||||
board: svg.id || "plateau",
|
||||
side: checker.matches(".checker-computer") ? "computer" : "player",
|
||||
on: BACKGROUNDS.find(([c]) => under.classList.contains(c))[1],
|
||||
edge: ratio(style.stroke, ground),
|
||||
fill: ratio(style.fill, ground),
|
||||
});
|
||||
}
|
||||
}
|
||||
return found;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Règle de la recette (recette-ux.md, « Pions de l'ordinateur lisibles
|
||||
* partout ») : là où un pion de l'ordinateur ne se détache pas de son fond
|
||||
* par sa couleur (moins de 3:1 : sur la barre, les flèches sombres, le fond
|
||||
* sombre), son contour s'en détache à 3:1 au moins (guide de marque :
|
||||
* contours porteurs de sens à 3:1). contrasts : le résultat de
|
||||
* checkerContrasts. Renvoie la liste des écarts, décrits (vide si tout va
|
||||
* bien).
|
||||
*/
|
||||
export function checkerContrastProblems(contrasts) {
|
||||
const problems = new Set();
|
||||
for (const c of contrasts) {
|
||||
if (c.side === "computer" && c.fill < 3 && c.edge < 3) {
|
||||
problems.add(`pion de l'ordinateur sur ${c.on} (${c.board}) : contour à ${c.edge.toFixed(2)}:1`);
|
||||
}
|
||||
}
|
||||
return [...problems];
|
||||
}
|
||||
|
||||
/** Le plus faible contraste du contour, par camp et par fond, pour le
|
||||
* journal du test (« ordinateur sur barre 6.79:1, … »). */
|
||||
export function checkerContrastSummary(contrasts) {
|
||||
const lowest = new Map();
|
||||
for (const c of contrasts) {
|
||||
const key = `${c.side === "computer" ? "ordinateur" : "joueur"} sur ${c.on}`;
|
||||
lowest.set(key, Math.min(lowest.get(key) ?? Number.POSITIVE_INFINITY, c.edge));
|
||||
}
|
||||
return [...lowest].map(([key, edge]) => `${key} ${edge.toFixed(2)}:1`).join(", ");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user