Charte graphique : copie du design system #14

Merged
pierre merged 1 commits from charte into main 2026-11-03 00:22:30 +00:00
8 changed files with 898 additions and 1 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ couleur, en monochrome et en taille réelle d'icône (48 et 24 px).
noyer et brique, comme les flèches d'un plateau. En monochrome, les quatre
flèches prennent la couleur du texte.
- **Icône d'application** : symbole sur fond ivoire `#F3EAD8`.
- **Police** : Alegreya Sans (SIL Open Font License 1.1), pour le logotype et,
- **Police** : Alegreya Sans (SIL Open Font License 1.1), pour le logotype et
pour l'interface. Pierre a validé cette dépendance, ainsi que fontTools
(MIT), outil de développement qui sert à régénérer le logotype, le
2026-11-03.
+5
View File
@@ -46,3 +46,8 @@ python3 -I docs/identite/generer-logo.py build/police/AlegreyaSans-Bold.ttf docs
Les PNG sont des rendus des SVG par Chrome sans interface
(`google-chrome --headless --screenshot`), à la taille indiquée dans leur nom.
## Charte graphique
La charte complète (couleurs des deux thèmes, typographie, composants) est dans
`charte/`, avec les liens vers le design system et les maquettes d'écrans.
+41
View File
@@ -0,0 +1,41 @@
# Charte graphique d'Ustagammon
Copie de travail du design system, pour l'appliquer à la PWA sans dépendre
d'un accès à claude.ai. La référence vivante, avec aperçus dans les deux thèmes :
- Design system : https://claude.ai/artifact/K2Z7MPE58XXJzv19n5r11M
- Maquettes des écrans (accueil, plateau, leçon, fin de partie, thème sombre) :
https://claude.ai/artifact/CQw6vbDpi6XQqVWQa4sANe
Décisions : ADR 0010. Fichiers du logo et des icônes : `../logo/`.
## Fichiers
| Fichier | Contenu |
| --- | --- |
| `guide-de-marque.md` | Règles d'usage : ton, couleurs, typographie, formes, mouvement, icônes, composants |
| `tokens.json` | Toutes les valeurs, thèmes clair et sombre, avec une note d'usage par couleur |
| `bundle.css` | Styles des composants ; mêmes noms de classes que `web/app.css` |
| `board.js` | Dessin de référence du plateau (repère 640 × 520), pour comparaison |
## Appliquer la charte à `web/`
1. **Couleurs** : les variables du plateau de `web/app.css` (`--frame`, `--bed`,
`--point-a`, `--checker-player`…) ont les mêmes noms dans `tokens.json` ;
reprendre leurs valeurs, thème clair dans `:root`, thème sombre dans le bloc
`prefers-color-scheme: dark`. Remplacer l'accent vert provisoire par la
brique (`--accent`, `--highlight`, `--target`) et ajouter les couleurs
nouvelles (`surface`, `accent-soft`, `verdict-*`, `focus`, `last-move` en laiton).
2. **Police** : embarquer Alegreya Sans (Regular, Italic, Medium, Bold,
ExtraBold) dans `web/`, avec sa licence OFL, en `@font-face`, et l'ajouter
au cache du service worker (hors ligne).
3. **Composants** : reprendre de `bundle.css` les boutons ronds
(`radius-pill`), le filet intérieur des pions, l'anneau de focus et la zone
de consigne.
4. **Icônes et logo** : remplacer `web/icons/` par les fichiers de
`../logo/` (icônes any et maskable, favicon), mettre le logo horizontal
dans la barre haute, et la couleur `theme-color` du manifeste à `#4A2E1F`.
Les contrastes de tous les textes ont été vérifiés par calcul (4,5:1 au moins
dans les deux thèmes) ; les marques du plateau, à 3:1 contre au moins un de
leurs voisins.
+74
View File
@@ -0,0 +1,74 @@
/* Dessin de référence du plateau Ustagammon, en SVG, avec les classes de bundle.css.
* Repère : 640 × 520, comme web/app.css (.board, aspect-ratio 640 / 520).
* Numérotation du point de vue du joueur : 1 à 12 en bas (de droite à gauche),
* 13 à 24 en haut (de gauche à droite). Le joueur a les pions ivoire. */
window.UstaBoard = (function () {
var W = 640, H = 520, SIDE = 16, TB = 28, PW = 43, BAR = 40, PH = 190, R = 20;
var TRAY_X = 580, TRAY_W = 44;
function colX(col) { return SIDE + col * PW + (col >= 6 ? BAR : 0); }
function place(point) { // point 1..24 → {col, top}
return point <= 12 ? { col: 12 - point, top: false } : { col: point - 13, top: true };
}
function checker(cx, cy, side, extra) {
var c = side === "player" ? "player" : "computer";
return '<circle class="checker checker-' + c + (extra ? " " + extra : "") + '" cx="' + cx + '" cy="' + cy + '" r="' + R + '"/>' +
'<circle class="checker-ring ring-' + c + '" cx="' + cx + '" cy="' + cy + '" r="12"/>';
}
function die(x, y, n, side) {
var s = 36, p = { 1: [[.5, .5]], 2: [[.27, .27], [.73, .73]], 3: [[.27, .27], [.5, .5], [.73, .73]],
4: [[.27, .27], [.73, .27], [.27, .73], [.73, .73]], 5: [[.27, .27], [.73, .27], [.5, .5], [.27, .73], [.73, .73]],
6: [[.27, .25], [.73, .25], [.27, .5], [.73, .5], [.27, .75], [.73, .75]] }[n];
var out = '<rect class="die-' + side + '" x="' + x + '" y="' + y + '" width="' + s + '" height="' + s + '" rx="6"/>';
p.forEach(function (q) { out += '<circle class="pip-' + side + '" cx="' + (x + q[0] * s) + '" cy="' + (y + q[1] * s) + '" r="3.6"/>'; });
return out;
}
/* state = { checkers: {point: [side, n]}, selected: point, movable: [points],
* targets: [points], lastMove: [points], dice: [a, b], diceSide, cube } */
function svg(state) {
var o = '<svg class="board" viewBox="0 0 ' + W + ' ' + H + '" role="img" aria-label="Plateau de backgammon" xmlns="http://www.w3.org/2000/svg">';
o += '<rect class="board-frame" x="0" y="0" width="' + W + '" height="' + H + '" rx="10"/>';
o += '<rect class="board-bed" x="' + SIDE + '" y="' + TB + '" width="' + (TRAY_X - 8 - SIDE) + '" height="' + (H - 2 * TB) + '"/>';
o += '<rect class="board-bar" x="' + (SIDE + 6 * PW) + '" y="' + TB + '" width="' + BAR + '" height="' + (H - 2 * TB) + '"/>';
o += '<rect class="board-tray" x="' + TRAY_X + '" y="' + TB + '" width="' + TRAY_W + '" height="' + (H / 2 - TB - 4) + '" rx="4"/>';
o += '<rect class="board-tray" x="' + TRAY_X + '" y="' + (H / 2 + 4) + '" width="' + TRAY_W + '" height="' + (H / 2 - TB - 4) + '" rx="4"/>';
for (var point = 1; point <= 24; point++) {
var pl = place(point), x = colX(pl.col), cls = (pl.col % 2 === 0) === pl.top ? "point-a" : "point-b";
var base = pl.top ? TB : H - TB, apex = pl.top ? TB + PH : H - TB - PH;
o += '<polygon class="' + cls + '" points="' + x + ',' + base + ' ' + (x + PW) + ',' + base + ' ' + (x + PW / 2) + ',' + apex + '"/>';
if ((state.targets || []).indexOf(point) >= 0)
o += '<polygon class="target-zone" points="' + x + ',' + base + ' ' + (x + PW) + ',' + base + ' ' + (x + PW / 2) + ',' + apex + '"/>';
if ((state.lastMove || []).indexOf(point) >= 0)
o += '<rect class="last-move" x="' + (x + 6) + '" y="' + (pl.top ? TB : H - TB - 5) + '" width="' + (PW - 12) + '" height="5" rx="2.5"/>';
o += '<text class="point-number" x="' + (x + PW / 2) + '" y="' + (pl.top ? 19 : H - 9) + '">' + point + '</text>';
var c = (state.checkers || {})[point];
if (c) {
var shown = Math.min(c[1], 5);
for (var i = 0; i < shown; i++) {
var cy = pl.top ? TB + R + 1 + i * (2 * R + 1) : H - TB - R - 1 - i * (2 * R + 1);
var extra = "";
if (i === shown - 1 && state.selected === point) extra = "selected";
else if (i === shown - 1 && (state.movable || []).indexOf(point) >= 0) extra = "movable";
o += checker(x + PW / 2, cy, c[0], extra);
if (i === shown - 1 && c[1] > 5)
o += '<text class="checker-count count-' + c[0] + '" x="' + (x + PW / 2) + '" y="' + (cy + 6) + '">' + c[1] + '</text>';
}
}
}
if (state.dice) {
var dx = colX(8) + 6, dy = H / 2 - 18, side = state.diceSide || "player";
o += die(dx, dy, state.dice[0], side) + die(dx + 46, dy, state.dice[1], side);
}
if (state.cube) {
var bx = SIDE + 6 * PW + 2;
o += '<rect class="cube-face" x="' + bx + '" y="' + (H / 2 - 18) + '" width="36" height="36" rx="6"/>';
o += '<text class="cube-value" x="' + (bx + 18) + '" y="' + (H / 2 + 6) + '">' + state.cube + '</text>';
}
return o + '</svg>';
}
var OPENING = { 24: ["player", 2], 13: ["player", 5], 8: ["player", 3], 6: ["player", 5],
1: ["computer", 2], 12: ["computer", 5], 17: ["computer", 3], 19: ["computer", 5] };
return { svg: svg, OPENING: OPENING };
})();
+165
View File
@@ -0,0 +1,165 @@
/* Ustagammon — styles des composants.
* Toutes les valeurs viennent de tokens.json (variables CSS du même nom).
* Les noms de classes reprennent ceux de web/app.css : ce fichier peut être
* recopié dans l'application presque tel quel. */
body {
margin: 0;
font-family: var(--font-sans);
font-size: 17px;
line-height: 25px;
color: var(--text);
background: var(--background);
}
a { color: var(--accent); text-underline-offset: 3px; }
:focus-visible {
outline: 3px solid var(--focus);
outline-offset: 2px;
}
/* ------------------------------------------------------------ Barre haute */
.topbar {
display: flex;
gap: var(--space-2);
align-items: center;
padding-block: var(--space-2);
}
.topbar .brand { flex: 1; display: flex; align-items: center; min-width: 0; }
.topbar .brand img { height: 32px; width: auto; display: block; }
.topbar h1 { flex: 1; margin: 0; font-size: 20px; line-height: 26px; font-weight: 700; }
/* ---------------------------------------------------------------- Boutons */
button, .button {
min-height: var(--button-height);
padding: 0 20px;
font: inherit;
font-weight: 700;
color: var(--on-accent);
background: var(--accent);
border: 2px solid var(--accent);
border-radius: var(--radius-pill);
cursor: pointer;
}
button.secondary, .button.secondary {
color: var(--text);
background: transparent;
border-color: var(--border-strong);
}
button.quiet, .button.quiet {
min-height: 44px;
padding: 0 12px;
color: var(--text);
background: transparent;
border-color: transparent;
font-weight: 500;
}
.topbar button { min-height: 44px; padding: 0 14px; font-size: 15px; }
button:disabled { opacity: 0.45; cursor: default; }
.actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.actions button { flex: 1; }
/* ------------------------------------------------------- Zone de consigne */
.pips { margin: var(--space-1) 0 0; font-size: 15px; line-height: 20px; color: var(--muted); text-align: center; font-variant-numeric: tabular-nums; }
.info, .prompt { min-height: 25px; margin: 6px 0; }
.info { color: var(--muted); }
.prompt { font-weight: 700; }
.prompt.thinking::after { content: ""; animation: thinking 1.2s steps(4, end) infinite; }
@keyframes thinking { 0% { content: ""; } 25% { content: "."; } 50% { content: ".."; } 75% { content: "..."; } }
@media (prefers-reduced-motion: reduce) { .prompt.thinking::after { animation: none; content: "…"; } }
/* ---------------------------------------------------------------- Plateau */
.board { display: block; width: 100%; height: auto; user-select: none; -webkit-tap-highlight-color: transparent; }
.board-frame { fill: var(--frame); }
.board-bed { fill: var(--bed); }
.board-bar { fill: var(--bar); }
.board-tray { fill: var(--tray); }
.point-a { fill: var(--point-a); }
.point-b { fill: var(--point-b); }
.point-number { fill: var(--point-number); font-size: 13px; font-weight: 500; text-anchor: middle; font-family: var(--font-sans); }
.checker { stroke-width: 2; }
.checker-player { fill: var(--checker-player); stroke: var(--checker-player-edge); }
.checker-computer { fill: var(--checker-computer); stroke: var(--checker-computer-edge); }
.checker-ring { fill: none; stroke-width: 1.5; }
.checker-player + .checker-ring, .ring-player { stroke: var(--checker-player-edge); }
.ring-computer { stroke: var(--checker-computer-edge); }
.checker.movable { stroke: var(--highlight); stroke-width: 3; }
.checker.selected { stroke: var(--highlight); stroke-width: 6; }
.checker-count { font-size: 18px; font-weight: 700; text-anchor: middle; font-family: var(--font-sans); }
.count-player { fill: var(--count-player); }
.count-computer { fill: var(--count-computer); }
.target-zone { fill: var(--target); }
.last-move { fill: var(--last-move); }
.die-player { fill: var(--die-player); stroke: var(--checker-player-edge); stroke-width: 1.5; }
.die-computer { fill: var(--die-computer); stroke: var(--checker-computer-edge); stroke-width: 1.5; }
.pip-player { fill: var(--pip-player); }
.pip-computer { fill: var(--pip-computer); }
.cube-face { fill: var(--cube); stroke: var(--checker-player-edge); stroke-width: 2; }
.cube-value { fill: var(--cube-text); font-size: 17px; font-weight: 800; text-anchor: middle; font-family: var(--font-sans); }
/* ------------------------------------------------------------------ Cartes */
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
box-shadow: var(--shadow-raised);
padding: var(--space-4);
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.overline { font-size: 13px; line-height: 16px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
/* --------------------------------------------------- Appréciation d'un coup */
.verdict {
display: inline-flex;
align-items: center;
gap: var(--space-1);
font-weight: 700;
font-size: 15px;
line-height: 20px;
}
.verdict svg { width: 18px; height: 18px; flex: none; }
.verdict-good { color: var(--verdict-good); }
.verdict-doubtful { color: var(--verdict-doubtful); }
.verdict-bad { color: var(--verdict-bad); }
/* -------------------------------------------------------- Fiche de glossaire */
.glossary-card h3 { margin: 0; font-size: 20px; line-height: 26px; font-weight: 700; }
.glossary-card .en { color: var(--muted); font-size: 15px; line-height: 20px; font-style: italic; }
.glossary-card p { margin: 0; }
.glossary-card .see { font-size: 15px; line-height: 20px; color: var(--muted); }
/* ------------------------------------------------ Demander à une IA (prompt) */
.ask-ai { display: flex; flex-direction: column; gap: var(--space-2); }
.ask-ai pre {
margin: 0;
max-height: 7.5em;
overflow: auto;
padding: var(--space-3);
background: var(--surface-sunken);
border-radius: var(--radius-md);
font: 15px/20px var(--font-sans);
white-space: pre-wrap;
color: var(--text);
}
.ask-ai .done { color: var(--verdict-good); font-weight: 700; font-size: 15px; }
/* ---------------------------------------------------------- Carte de leçon */
.lesson-card { flex-direction: row; align-items: center; gap: var(--space-3); }
.lesson-card .body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.lesson-card h3 { margin: 0; font-size: 17px; line-height: 25px; font-weight: 700; }
.lesson-card .state { font-size: 15px; line-height: 20px; color: var(--muted); }
.progress { height: 6px; border-radius: var(--radius-pill); background: var(--surface-sunken); overflow: hidden; }
.progress > span { display: block; height: 100%; background: var(--accent); border-radius: inherit; }
+61
View File
@@ -0,0 +1,61 @@
Ustagammon apprend le backgammon comme on l'apprend à une table de café : quelqu'un qui sait montre à quelqu'un qui découvre. L'interface est **chaleureuse, artisanale, claire et intemporelle**, discrètement turque par la matière (le bois, l'ivoire, la brique), jamais par le folklore. Elle doit donner envie à un débutant sans paraître naïve à un joueur confirmé : un maître bienveillant, pas un logiciel d'analyse, pas un casino.
Ce système s'applique à la PWA (`web/`). Les noms des couleurs du plateau sont exactement ceux des variables de `web/app.css` ; `components/bundle.css` reprend ses noms de classes et peut y être recopié.
## Contenus et ton
- **Vouvoiement**, phrases courtes, verbes d'action. Le joueur est « vous », l'adversaire est « l'ordinateur » (ou le nom d'un joueur réel).
- **Les mots du jeu**, toujours les mêmes, ceux du glossaire (`docs/glossaire/`) : flèche, pion, barre, videau, compte de points, sortir. Un terme technique s'explique la première fois qu'il apparaît, avec un lien vers sa fiche.
- **Les boutons disent ce qu'ils font** : « Lancer les dés », « Annuler », « Doubler », « Prendre », « Passer », « Nouvelle partie ». Pas de « OK », pas de « Valider ». Les textes de l'app vivent dans `app/src/ui/texts.ts`.
- **Le maître encourage, il ne juge pas.** « Bien joué : ce coup protège votre point 8. » plutôt que « Coup correct (−0,012) ». Les chiffres d'équité ne viennent que sur demande.
- **Les erreurs disent quoi faire** : « Ce pion ne peut pas aller là : la flèche est tenue par deux pions adverses. »
- **Ni émoji, ni point d'exclamation en série, ni majuscules de réclame.** Les capitales sont réservées au style `overline`.
- Le nom s'écrit **Ustagammon**, en un mot, majuscule initiale.
## Couleurs
La palette, c'est le bois, les pions et le café, avec **un seul accent : le rouge brique** (`accent`).
- Fond de page `background`, cartes et panneaux `surface`, zones en retrait `surface-sunken`.
- Texte `text` ; texte secondaire `muted`, jamais pour une consigne.
- `accent` est rare et a toujours un sens : le bouton principal de l'écran (un seul), un lien, le pion sélectionné (`highlight`), la case où il peut aller (`target`), le focus clavier (`focus`). Ce n'est jamais une couleur de décoration ni une couleur d'erreur.
- `accent-soft` : fond léger pour signaler « c'est à vous », avec du texte `text` dessus.
- Les appréciations de coups ont leurs propres couleurs, distinctes de la brique : `verdict-good` (vert), `verdict-doubtful` (ocre), `verdict-bad` (lie-de-vin). **Toujours avec le mot et l'icône**, jamais la couleur seule.
- `brand-laiton` (via `last-move`) marque le dernier coup joué, comme les charnières en laiton d'un plateau de tavla. Rien d'autre.
- Le plateau : cadre `frame` en noyer, fond `bed`, flèches alternées `point-a` (noyer clair) et `point-b` (chêne), barre `bar`, sorties `tray`. Le joueur a les pions ivoire (`checker-player`, l'apprenti), l'ordinateur les pions noyer (`checker-computer`, le maître), comme dans le logo.
- **À éviter** : le vert feutre de casino, le noir et or, les dégradés, les néons.
Les deux thèmes, clair et sombre, sont complets. Tous les textes atteignent 4,5:1 sur leurs fonds dans les deux thèmes ; les contours et marques porteuses de sens, 3:1.
## Typographie
Une seule famille, **Alegreya Sans** (licence SIL OFL, fichiers dans `fonts/`), sans empattement humaniste, chaleureuse et lisible en petit. À embarquer dans l'app : elle doit fonctionner hors ligne.
- `display` (32px, 800) : un seul par écran, accueil et fin de partie.
- `title` (24px) : titre d'écran ; `heading` (20px) : titre de section ou de carte.
- `body` (17px) : tout le texte à lire, jamais plus petit. `body-strong` pour la consigne du moment et les boutons. `body-italic` pour la parole du maître ou un exemple.
- `caption` (15px) : compte de points, légendes. `overline` (13px, capitales espacées) : étiquette au-dessus d'un titre, avec parcimonie.
- Chiffres alignés (`font-variant-numeric: tabular-nums`) dès que des nombres se comparent (compte de points, scores).
## Formes, espace et profondeur
- **Boutons ronds comme des pions** : `radius-pill`, hauteur `button-height` (48px). Aucune cible tactile sous 44px.
- Cartes et fiches : `radius-md`, contour `border`, ombre `shadow-raised` et rien de plus profond. Dés et videau : `radius-sm`.
- Espacements : `space-1` à `space-8`. Gouttière de page `space-4` sur téléphone ; écart entre blocs `space-6`. Contenu limité à `page-width` sur grand écran.
- Le plateau occupe toute la largeur sur téléphone ; c'est lui le héros de l'écran de jeu, pas les panneaux autour.
- Focus clavier : anneau `focus` plein de 3px, décalé de 2px, sur tous les éléments interactifs.
## Mouvement
Sobre et utile : un pion glisse vers sa flèche (150 à 250 ms, sans rebond), les dés roulent brièvement. Les trois points de « l'ordinateur réfléchit » sont la seule animation en boucle. Tout s'arrête avec `prefers-reduced-motion`.
## Iconographie
- Le **symbole** (deux pions sur une flèche, le 8 caché) et le **logotype** (les « mm » en flèches) sont dans le groupe Logos ; versions couleur, sombre, noire et blanche. Ne jamais les redessiner, déformer ou recolorer.
- Barre haute : `logo-horizontal-couleur.svg` en thème clair, `logo-horizontal-sombre.svg` en thème sombre, 32px de haut.
- Icônes d'interface : traits simples de 2px à bouts ronds, dessinés en SVG dans le code (pas de police d'icônes, pas d'émoji). Les seules icônes du système pour l'instant : les trois appréciations de coups (coche, point d'interrogation, croix) et la copie (deux feuillets).
- Dés, pions et videau sont dessinés en SVG avec les couleurs du plateau, jamais en images.
## Composants
Plateau (`Board`) et ses pièces (`Checker`, `Die`, `DoublingCube`), boutons (`Button`), barre haute (`TopBar`), zone de consigne (`GamePrompt`), appréciation d'un coup (`MoveVerdict`), fiche de glossaire (`GlossaryCard`), carte de leçon (`LessonCard`) et « Demander à une IA » (`AskAI`). Chacun a sa fiche avec ses règles d'usage et un aperçu dans les deux thèmes.
+530
View File
@@ -0,0 +1,530 @@
{
"name": "Ustagammon",
"version": 1,
"color": {
"themes": [
{
"id": "light",
"name": "Clair"
},
{
"id": "dark",
"name": "Sombre"
}
],
"tokens": [
{
"name": "background",
"value": {
"light": "#F5EFE5",
"dark": "#17110D"
},
"usage": "Fond de page (papier ivoire / café torréfié)."
},
{
"name": "surface",
"value": {
"light": "#FFFCF6",
"dark": "#221913"
},
"usage": "Fond des cartes, fiches de glossaire, panneaux posés sur `background`."
},
{
"name": "surface-sunken",
"value": {
"light": "#ECE3D4",
"dark": "#2C211A"
},
"usage": "Zones en retrait : bloc de prompt à copier, piste de progression."
},
{
"name": "text",
"value": {
"light": "#2B1D15",
"dark": "#EFE5D5"
},
"usage": "Texte courant et titres, sur `background`, `surface` et `surface-sunken`."
},
{
"name": "muted",
"value": {
"light": "#6B5C50",
"dark": "#A99886"
},
"usage": "Texte secondaire (compte de points, légendes, aide), sur `background` et `surface`."
},
{
"name": "border",
"value": {
"light": "#DED2C0",
"dark": "#3A2D24"
},
"usage": "Séparateurs et contours décoratifs de cartes (ne porte aucun sens)."
},
{
"name": "border-strong",
"value": {
"light": "#8E7D6A",
"dark": "#7D6B5B"
},
"usage": "Contour des boutons secondaires et des champs : 3:1 sur `background` et `surface`."
},
{
"name": "accent",
"value": {
"light": "#A8432B",
"dark": "#D0694B"
},
"usage": "Rouge brique, l'accent unique : bouton principal, liens, case où le pion peut aller, focus. Texte en brique sur `background` et `surface`."
},
{
"name": "on-accent",
"value": {
"light": "#FFFFFF",
"dark": "#1A110C"
},
"usage": "Texte et icônes posés sur un aplat `accent`."
},
{
"name": "accent-soft",
"value": {
"light": "#F1D9CF",
"dark": "#4A2519"
},
"usage": "Fond léger d'accent : puce « à toi de jouer », sélection dans une liste. Texte `text` dessus."
},
{
"name": "focus",
"value": {
"light": "#A8432B",
"dark": "#E58A6E"
},
"usage": "Anneau de focus clavier, 3 px plein, décalé de 2 px : 3:1 sur tous les fonds."
},
{
"name": "error",
"value": {
"light": "#8C1C3A",
"dark": "#F08FA6"
},
"usage": "Message d'erreur technique (texte), toujours accompagné d'un mot. Lie-de-vin, distinct de la brique."
},
{
"name": "verdict-good",
"value": {
"light": "#2F6B4F",
"dark": "#7CCBA4"
},
"usage": "« Bon coup » : texte et icône, toujours avec le mot. Sur `background` et `surface`."
},
{
"name": "verdict-doubtful",
"value": {
"light": "#7E5A0F",
"dark": "#E0B04F"
},
"usage": "« Imprécision » (doubtful) : texte et icône, toujours avec le mot."
},
{
"name": "verdict-bad",
"value": {
"light": "#8C1C3A",
"dark": "#F08FA6"
},
"usage": "« Erreur » et « grosse erreur » (bad, very bad) : texte et icône, toujours avec le mot."
},
{
"name": "brand-noyer",
"value": {
"light": "#4A2E1F",
"dark": "#4A2E1F"
},
"usage": "Noyer du logo : pion maître. Ne sert pas au texte de l'interface."
},
{
"name": "brand-ivoire",
"value": {
"light": "#F3EAD8",
"dark": "#F3EAD8"
},
"usage": "Ivoire du logo : pion apprenti, fond de l'icône d'application."
},
{
"name": "brand-laiton",
"value": {
"light": "#8F6A1C",
"dark": "#D6A64A"
},
"usage": "Laiton, comme les charnières d'un plateau de tavla : marque du dernier coup joué, rien d'autre."
},
{
"name": "frame",
"value": {
"light": "#4A2E1F",
"dark": "#2A1A10"
},
"usage": "Cadre du plateau (noyer)."
},
{
"name": "bed",
"value": {
"light": "#E9DCC4",
"dark": "#4A3626"
},
"usage": "Fond du plateau, entre les flèches."
},
{
"name": "bar",
"value": {
"light": "#3F2619",
"dark": "#24170E"
},
"usage": "Barre centrale."
},
{
"name": "tray",
"value": {
"light": "#3A2418",
"dark": "#22150D"
},
"usage": "Plateaux de sortie, à droite."
},
{
"name": "point-a",
"value": {
"light": "#6B4430",
"dark": "#3A2519"
},
"usage": "Flèches sombres (noyer clair), une sur deux."
},
{
"name": "point-b",
"value": {
"light": "#C9AE88",
"dark": "#7E6142"
},
"usage": "Flèches claires (chêne), une sur deux."
},
{
"name": "point-number",
"value": {
"light": "#F3EAD8",
"dark": "#CDBFA8"
},
"usage": "Numéros des flèches, posés sur `frame`."
},
{
"name": "checker-player",
"value": {
"light": "#F7F0E2",
"dark": "#EDE3CF"
},
"usage": "Pions du joueur : ivoire, l'apprenti."
},
{
"name": "checker-player-edge",
"value": {
"light": "#8C7A62",
"dark": "#8E7C64"
},
"usage": "Contour et filet des pions du joueur."
},
{
"name": "checker-computer",
"value": {
"light": "#2E1C12",
"dark": "#140D09"
},
"usage": "Pions de l'ordinateur : noyer sombre, le maître."
},
{
"name": "checker-computer-edge",
"value": {
"light": "#120A05",
"dark": "#A8957D"
},
"usage": "Contour et filet des pions de l'ordinateur ; clair en thème sombre pour rester visible."
},
{
"name": "count-player",
"value": {
"light": "#2B1D15",
"dark": "#2B1D15"
},
"usage": "Nombre écrit sur une pile de pions du joueur."
},
{
"name": "count-computer",
"value": {
"light": "#F3EAD8",
"dark": "#F3EAD8"
},
"usage": "Nombre écrit sur une pile de pions de l'ordinateur."
},
{
"name": "die-player",
"value": {
"light": "#F7F0E2",
"dark": "#EDE3CF"
},
"usage": "Dés du joueur."
},
{
"name": "pip-player",
"value": {
"light": "#2B1D15",
"dark": "#2B1D15"
},
"usage": "Points des dés du joueur."
},
{
"name": "die-computer",
"value": {
"light": "#2E1C12",
"dark": "#140D09"
},
"usage": "Dés de l'ordinateur."
},
{
"name": "pip-computer",
"value": {
"light": "#F3EAD8",
"dark": "#EDE3CF"
},
"usage": "Points des dés de l'ordinateur."
},
{
"name": "cube",
"value": {
"light": "#F7F0E2",
"dark": "#EDE3CF"
},
"usage": "Face du videau."
},
{
"name": "cube-text",
"value": {
"light": "#2B1D15",
"dark": "#2B1D15"
},
"usage": "Valeur du videau (2, 4, 8…)."
},
{
"name": "highlight",
"value": {
"light": "#A8432B",
"dark": "#E07A5A"
},
"usage": "Contour d'un pion jouable ou sélectionné du joueur : la brique. Il contraste à 3:1 au moins avec le pion ivoire (clair) ou avec le fond du plateau (sombre)."
},
{
"name": "target",
"value": {
"light": "rgba(168,67,43,0.32)",
"dark": "rgba(224,122,90,0.38)"
},
"usage": "Voile sur la flèche où le pion sélectionné peut aller."
},
{
"name": "last-move",
"value": {
"light": "{brand-laiton}",
"dark": "{brand-laiton}"
},
"usage": "Marque du dernier coup joué (laiton)."
}
]
},
"type": {
"fonts": [
{
"family": "Alegreya Sans",
"file": "fonts/AlegreyaSans-Regular.ttf",
"weight": "400"
},
{
"family": "Alegreya Sans",
"file": "fonts/AlegreyaSans-Italic.ttf",
"weight": "400",
"style": "italic"
},
{
"family": "Alegreya Sans",
"file": "fonts/AlegreyaSans-Medium.ttf",
"weight": "500"
},
{
"family": "Alegreya Sans",
"file": "fonts/AlegreyaSans-Bold.ttf",
"weight": "700"
},
{
"family": "Alegreya Sans",
"file": "fonts/AlegreyaSans-ExtraBold.ttf",
"weight": "800"
}
],
"families": {
"sans": "\"Alegreya Sans\", \"Gill Sans\", \"Segoe UI\", system-ui, sans-serif"
},
"groups": [
{
"name": "Titres",
"family": "sans",
"styles": [
{
"name": "display",
"fontSize": "32px",
"lineHeight": "36px",
"fontWeight": 800,
"letterSpacing": "-0.01em",
"sample": "Une partie contre le maître",
"usage": "Titre d'accueil et de fin de partie, une fois par écran."
},
{
"name": "title",
"fontSize": "24px",
"lineHeight": "30px",
"fontWeight": 700,
"sample": "Le point 8",
"usage": "Titre d'écran (leçon, aide)."
},
{
"name": "heading",
"fontSize": "20px",
"lineHeight": "26px",
"fontWeight": 700,
"sample": "Les ouvertures",
"usage": "Sous-titre de section, titre de carte."
}
]
},
{
"name": "Texte",
"family": "sans",
"styles": [
{
"name": "body",
"fontSize": "17px",
"lineHeight": "25px",
"fontWeight": 400,
"sample": "Ce pion peut rejoindre votre point 8.",
"usage": "Texte courant ; jamais en dessous de 17px pour le contenu à lire."
},
{
"name": "body-strong",
"fontSize": "17px",
"lineHeight": "25px",
"fontWeight": 700,
"sample": "À vous de jouer : 6 et 1.",
"usage": "Consigne du moment (zone prompt), boutons."
},
{
"name": "body-italic",
"fontSize": "17px",
"lineHeight": "25px",
"fontWeight": 400,
"fontStyle": "italic",
"sample": "Philippe aurait joué 13/7 8/7.",
"usage": "Parole du maître, citation, exemple."
},
{
"name": "caption",
"fontSize": "15px",
"lineHeight": "20px",
"fontWeight": 400,
"sample": "Vous : 167 · Ordinateur : 152",
"usage": "Compte de points, légendes, mentions."
},
{
"name": "overline",
"fontSize": "13px",
"lineHeight": "16px",
"fontWeight": 700,
"letterSpacing": "0.08em",
"sample": "LEÇON 3 SUR 12",
"usage": "Étiquette en capitales au-dessus d'un titre ; avec parcimonie."
}
]
}
]
},
"spacing": {
"tokens": [
{
"name": "space-1",
"value": "4px",
"usage": "Écart entre une icône et son texte."
},
{
"name": "space-2",
"value": "8px",
"usage": "Écart entre boutons voisins, marges internes serrées."
},
{
"name": "space-3",
"value": "12px",
"usage": "Marge interne des puces et du bloc prompt."
},
{
"name": "space-4",
"value": "16px",
"usage": "Gouttière latérale de page sur téléphone, marge interne des cartes."
},
{
"name": "space-6",
"value": "24px",
"usage": "Écart entre blocs d'un écran."
},
{
"name": "space-8",
"value": "32px",
"usage": "Écart entre sections."
},
{
"name": "button-height",
"value": "48px",
"usage": "Hauteur des boutons ; aucune cible tactile sous 44px."
},
{
"name": "page-width",
"value": "40rem",
"usage": "Largeur maximale du contenu sur grand écran."
}
]
},
"radius": {
"tokens": [
{
"name": "radius-sm",
"value": "6px",
"usage": "Dés, videau, puces de verdict."
},
{
"name": "radius-md",
"value": "12px",
"usage": "Cartes, fiches, bloc prompt."
},
{
"name": "radius-pill",
"value": "999px",
"usage": "Boutons : ronds comme des pions."
}
]
},
"shadow": {
"tokens": [
{
"name": "shadow-raised",
"value": {
"light": "0 1px 2px rgba(43,29,21,0.10)",
"dark": "0 1px 2px rgba(0,0,0,0.40)"
},
"usage": "Seule ombre permise : cartes posées sur le fond. Rien de plus profond."
}
]
},
"meta": {
"source": "Brief créatif + ADR 0010 + web/app.css (noms des variables du plateau)"
}
}
+21
View File
@@ -89,3 +89,24 @@ Une entrée datée par session : ce qui a été fait, décidé, et la suite.
- Sous Firefox sur PC, un tour de l'ordinateur a pris jusqu'à 5,1 s
(Chromium : 0,4 s) ; Firefox sur le téléphone reste à mesurer.
- Validation par Pierre de Playwright (Apache-2.0), outil de `make test-browser`.
## 2026-11-03 (nuit) : identité visuelle
**Fait**
- Identité choisie par Pierre en trois tours sur une planche de travail :
accent brique, symbole « P3d » (deux pions sur une flèche, le 8 caché),
logotype « L3 » (les « mm » en flèches), icône sur fond ivoire. ADR 0010,
fichiers du logo et des icônes dans `docs/identite/logo/` (MR !12).
- Design system publié (https://claude.ai/artifact/K2Z7MPE58XXJzv19n5r11M) :
couleurs des deux thèmes reprenant les noms de variables de `web/app.css`,
Alegreya Sans, onze composants, contrastes vérifiés par calcul. Copie dans
`docs/identite/charte/`.
- Maquettes des quatre écrans et du thème sombre
(https://claude.ai/artifact/CQw6vbDpi6XQqVWQa4sANe).
**Reste à faire**
- Appliquer la charte à la PWA (couleurs, police embarquée, icônes, logo) :
à planifier pour l'agent développeur, voir `docs/identite/charte/README.md`.
- Les maquettes proposent des éléments de produit qui ne sont pas encore
décidés (leçons, « Conseil du maître », bilan de fin de partie) : à valider
par Pierre avant toute mise en œuvre.