Un fichier
pour toute l'apparence
design.json contient 29 valeurs nommées : couleurs, polices, tailles, espacements, effets. Le rédacteur ne manipule que des noms — la valeur exacte vit à un seul endroit, et un contraste insuffisant arrête la compilation.
Un fichier, toute l'apparence
site/design/design.json contient 29 valeurs nommées. Ensemble, elles décident de tout ce qui se voit : les couleurs, les polices, la taille des titres, l'espace entre les sections, la courbe des animations.
Le rédacteur n'écrit jamais #111111 ni clamp(2.9rem, 4rem + 5.9vw, 16rem). Il écrit noir et titre_xxl. La valeur exacte vit ici, à un seul endroit.
Mesuré. Changer la couleur creme dans ce fichier modifie deux lignes de la feuille de style produite — et repeint les 26 pages. Aucun fichier HTML n'est touché.
Cinq familles, un préfixe chacune
Chaque famille reçoit un préfixe court dans le CSS produit. Le préfixe est court parce qu'il se répète des centaines de fois dans la feuille — et il n'est jamais tapé à la main.
Une famille inconnue, ou un jeton absent de sa famille, arrête la compilation : CSS, lui, ignorerait silencieusement une variable inexistante et laisserait une règle morte.
| Famille | Préfixe CSS | Nombre | Ce qu'elle contrôle |
|---|---|---|---|
| couleurs | --c-… | 9 | fonds, textes, filets, bordures |
| typographie | --t-… | 7 | familles de polices, graisses, interlignes |
| tailles | --s-… | 5 | taille des titres et du texte courant |
| espacements | --e-… | 6 | marges, hauteur de la navigation, rythme vertical |
| effets | --f-… | 2 | courbes d'animation, arrondis |
Les 29 jetons, un par un
Ce tableau est lu dans le fichier au moment où cette page est engendrée. Il ne peut donc pas décrire un état périmé.
La colonne de droite donne le nom de la variable CSS produite : c'est elle qu'on retrouve dans la feuille de style, jamais la valeur brute.
Couleurs — 9 jetons
| Jeton | Valeur | Rôle | Variable CSS |
|---|---|---|---|
| noir | #111111 | Noir principal (textes, fonds sombres) | --c-noir |
| creme | #f4f2ee | Crème (fond du site) | --c-creme |
| blanc | #ffffff | Blanc (cartes) | --c-blanc |
| gris | #6e6e6e | Gris (textes secondaires) | --c-gris |
| gris_clair | #d8d6d2 | Gris clair (bordures, placeholders) | --c-gris_clair |
| gris_sombre | #a9a9a9 | Gris sur fond noir | --c-gris_sombre |
| encre | #1c1c1c | Encre (nuance sombre) | --c-encre |
| ligne | rgba(17,17,17,.16) | Couleur des filets et bordures | --c-ligne |
| gris_sur_creme | #6e6e6e | Gris sur fond clair (contraste AA garanti) | --c-gris_sur_creme |
Typographie — 7 jetons
| Jeton | Valeur | Rôle | Variable CSS |
|---|---|---|---|
| police_titres | "Futura","Jost","Century Gothic",sans-serif | Police des titres | --t-police_titres |
| police_texte | "Jost","Helvetica Neue",Arial,sans-serif | Police du texte | --t-police_texte |
| graisse_titres | 400 | Graisse des titres | --t-graisse_titres |
| graisse_texte | 300 | Graisse du texte courant | --t-graisse_texte |
| interligne_titres | 0.94 | Interligne des titres (serré = 0.9) | --t-interligne_titres |
| interligne_texte | 1.65 | Interligne du texte | --t-interligne_texte |
| espacement_kicker | 0.22em | Espacement des petites capitales | --t-espacement_kicker |
Tailles de texte — 5 jetons
| Jeton | Valeur | Rôle | Variable CSS |
|---|---|---|---|
| titre_xxl | clamp(2.9rem, 4rem + 5.9vw, 16rem) | Titre géant (accueil) | --s-titre_xxl |
| titre_xl | clamp(2.1rem, 2.2rem + 3.4vw, 9rem) | Grand titre de section | --s-titre_xl |
| titre_m | clamp(1.5rem, 1.1rem + 1.6vw, 4.5rem) | Titre moyen | --s-titre_m |
| texte | clamp(.95rem,.35vw + .88rem,1.0625rem) | Texte courant | --s-texte |
| kicker | .6875rem | Sur-titre en capitales | --s-kicker |
Espacements — 6 jetons
| Jeton | Valeur | Rôle | Variable CSS |
|---|---|---|---|
| marge_page | clamp(1.25rem,4.5vw,4.5rem) | Marge latérale du site | --e-marge_page |
| hauteur_nav | 72px | Hauteur barre de navigation (mobile) | --e-hauteur_nav |
| hauteur_nav_desktop | 102px | Hauteur barre de navigation (desktop) | --e-hauteur_nav_desktop |
| logo_mobile | 33px | Taille du logo (mobile) | --e-logo_mobile |
| logo_desktop | 49px | Taille du logo (desktop) | --e-logo_desktop |
| rythme_section | clamp(3rem,7vw,6rem) | Espace entre les sections | --e-rythme_section |
Effets — 2 jetons
| Jeton | Valeur | Rôle | Variable CSS |
|---|---|---|---|
| transition | cubic-bezier(.22,.61,.36,1) | Courbe d'animation | --f-transition |
| rayon_bouton | 999px | Arrondi des boutons | --f-rayon_bouton |
Le contraste est calculé, pas déclaré
La différence est tout le sujet. Un CMS qui affiche « contraste : conforme » parce que quelqu'un a coché une case ne garantit rien.
Ici, le rapport est calculé à chaque compilation à partir des valeurs réelles des jetons, et un échec arrête la publication.
composante linéarisée : c ≤ 0,03928 → c / 12,92
sinon → ((c + 0,055) / 1,055) ^ 2,4
luminance relative : L = 0,2126·R + 0,7152·V + 0,0722·B
rapport : (L_clair + 0,05) / (L_sombre + 0,05)
Les coefficients ne sont pas arbitraires : ils reflètent la sensibilité de l'œil humain, nettement plus élevée dans le vert que dans le bleu. C'est pourquoi un texte bleu sur fond noir peut échouer là où un vert de même clarté apparente passe.
W3C — WCAG 2.2, critère 1.4.3 (niveau AA)Les seuils appliqués, et le résultat mesuré
| Seuil | S'applique à | Critère |
|---|---|---|
| 4,5:1 | texte courant | WCAG 1.4.3 — AA |
| 3:1 | texte ≥ 24 px, ou ≥ 18,66 px en gras | WCAG 1.4.3 — AA |
| 3:1 | éléments d'interface, graphiques | WCAG 1.4.11 — AA |
| 7:1 | texte courant | WCAG 1.4.6 — AAA |
Sur la palette actuelle, la paire la plus serrée est gris sur crème : 4,56:1 — soit 0,06 au-dessus du seuil. C'est peu, et c'est mesuré : un gris légèrement plus clair ferait échouer la compilation.
Pourquoi une taille doit contenir des rem
C'est la règle la plus contre-intuitive du fichier, et elle vient d'un défaut mesuré sur l'ancien site.
Une taille de texte qui ne contient aucune composante rem ne suit pas le réglage de police du navigateur. Au zoom 200 %, elle ne grossit pas — elle peut même rétrécir.
Le défaut, et les trois tentatives de correction
Sur l'ancien site, 35 clamp() étaient écrits sans rem. Mesuré : le H1 passait de 152 px à 79 px au zoom 200 %. Échec WCAG 1.4.4, technique F94.
La correction a demandé trois essais, et les deux premiers n'ont rien changé :
- Plafond en
em— sans effet :emse résout contre la police de l'élément lui-même. max(clamp(…), rem)— sans effet non plus. La mesure a révélé pourquoi :11vwvalait déjà 158 px, sous le plafond de 152 px. C'étaitvwle limiteur, pas le plafond.- Mélanger
remetvwdans la composante fluide —clamp(2.9rem, 4rem + 5.9vw, 16rem). Résultat : ×1,43 au zoom, rendu à 2 % près de l'original.
Cette valeur est exactement celle du jeton titre_xxl ci-dessus.
# Une taille de texte DOIT contenir une composante rem, sinon elle
# ne suit pas le réglage de police du navigateur.
for famille in FAMILLES_TEXTE:
for nom, obj in self.brut.get(famille, {}).items():
v = str(obj["valeur"])
if "clamp(" in v or "vw" in v or "vh" in v:
if "rem" not in v and "em" not in v:
fautes.append(
f"tailles.{nom} = {v}\n"
" Aucune composante rem : au zoom 200 %\n"
" ce texte ne grossira pas "
"(WCAG 1.4.4, technique F94).")
Modifier l'apparence du site
Trois opérations, toutes dans ce seul fichier. Aucune ne touche au contenu, aucune ne touche au HTML.
Changer une couleur
Modifier la valeur, enregistrer, recompiler. La couleur change partout où le jeton est utilisé — et nulle part ailleurs.
"creme": {
"valeur": "#f4f2ee", ← la seule chose à changer
"label": "Crème (fond du site)"
}Si la nouvelle couleur fait passer une paire sous 4,5:1, la compilation s'arrête et nomme la paire fautive ainsi que le rapport obtenu.
Changer une police
Les deux jetons police_titres et police_texte contiennent une pile complète, avec ses polices de repli. La pile entière est remplacée d'un coup.
"police_titres": {
"valeur": "\"Futura\",\"Jost\",\"Century Gothic\",sans-serif",
"label": "Police des titres"
}Un piège évité par construction : la propriété raccourcie font: de CSS ne peut pas contenir de var() pour la famille — la déclaration entière devient invalide, en silence. Le convertisseur n'émet jamais de raccourci font: ; il écrit chaque propriété séparément.
Ajouter un jeton
C'est l'opération encadrée par docs/EXTENSION.md, et la règle y tient en une phrase :
Un nouveau jeton doit augmenter la liberté graphique en réduisant le nombre de choses fausses qu'il est possible d'écrire.
Ce n'est pas un jeu de mots. Un jeton bien conçu remplace une valeur libre — où toute erreur est possible — par un choix fini dont chaque option est correcte par construction.
| Approche | Ce qu'on écrit | Ce qui peut casser |
|---|---|---|
| Valeur libre | "taille": "4.5rem" | ne s'adapte pas ; casse sur mobile ; échappe au design system |
| Fausse contrainte | "taille": "clamp(2rem,8vw,5rem)" | aucun rem dans la borne basse → le texte rétrécit au zoom 200 % |
| Jeton | "taille": "xxl" | rien — la valeur est calculée, vérifiée, et le zoom fonctionne |