Charte graphique : copie du design system et journal
Couleurs des deux thèmes (mêmes noms que web/app.css), styles des composants, guide de marque et dessin de référence du plateau dans docs/identite/charte/, avec les liens vers le design system et les maquettes d'écrans. Coquille corrigée dans l'ADR 0010. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
65551098db
commit
f5d8d41a00
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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 };
|
||||
})();
|
||||
@@ -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; }
|
||||
@@ -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.
|
||||
@@ -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)"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user