Le CMS · 2

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.

Jetons
29
en 5 familles
Couleurs
9
toutes vérifiées WCAG
Contraste minimum
4,56:1
seuil AA à 4,5:1
Effet d'un changement
2 lignes
de CSS, 26 pages repeintes
§1

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.

Les jetons de design alimentent un point unique, d'où ils se diffusent dans toutes les pages. Changer une valeur les change toutes.
Les jetons de design alimentent un point unique, d'où ils se diffusent dans toutes les pages. Changer une valeur les change toutes.

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é.

§2

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.

FamillePréfixe CSSNombreCe qu'elle contrôle
couleurs--c-…9fonds, textes, filets, bordures
typographie--t-…7familles de polices, graisses, interlignes
tailles--s-…5taille des titres et du texte courant
espacements--e-…6marges, hauteur de la navigation, rythme vertical
effets--f-…2courbes d'animation, arrondis
§3

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

JetonValeurRôleVariable CSS
noir#111111Noir principal (textes, fonds sombres)--c-noir
creme#f4f2eeCrème (fond du site)--c-creme
blanc#ffffffBlanc (cartes)--c-blanc
gris#6e6e6eGris (textes secondaires)--c-gris
gris_clair#d8d6d2Gris clair (bordures, placeholders)--c-gris_clair
gris_sombre#a9a9a9Gris sur fond noir--c-gris_sombre
encre#1c1c1cEncre (nuance sombre)--c-encre
lignergba(17,17,17,.16)Couleur des filets et bordures--c-ligne
gris_sur_creme#6e6e6eGris sur fond clair (contraste AA garanti)--c-gris_sur_creme

Typographie — 7 jetons

JetonValeurRôleVariable CSS
police_titres"Futura","Jost","Century Gothic",sans-serifPolice des titres--t-police_titres
police_texte"Jost","Helvetica Neue",Arial,sans-serifPolice du texte--t-police_texte
graisse_titres400Graisse des titres--t-graisse_titres
graisse_texte300Graisse du texte courant--t-graisse_texte
interligne_titres0.94Interligne des titres (serré = 0.9)--t-interligne_titres
interligne_texte1.65Interligne du texte--t-interligne_texte
espacement_kicker0.22emEspacement des petites capitales--t-espacement_kicker

Tailles de texte — 5 jetons

JetonValeurRôleVariable CSS
titre_xxlclamp(2.9rem, 4rem + 5.9vw, 16rem)Titre géant (accueil)--s-titre_xxl
titre_xlclamp(2.1rem, 2.2rem + 3.4vw, 9rem)Grand titre de section--s-titre_xl
titre_mclamp(1.5rem, 1.1rem + 1.6vw, 4.5rem)Titre moyen--s-titre_m
texteclamp(.95rem,.35vw + .88rem,1.0625rem)Texte courant--s-texte
kicker.6875remSur-titre en capitales--s-kicker

Espacements — 6 jetons

JetonValeurRôleVariable CSS
marge_pageclamp(1.25rem,4.5vw,4.5rem)Marge latérale du site--e-marge_page
hauteur_nav72pxHauteur barre de navigation (mobile)--e-hauteur_nav
hauteur_nav_desktop102pxHauteur barre de navigation (desktop)--e-hauteur_nav_desktop
logo_mobile33pxTaille du logo (mobile)--e-logo_mobile
logo_desktop49pxTaille du logo (desktop)--e-logo_desktop
rythme_sectionclamp(3rem,7vw,6rem)Espace entre les sections--e-rythme_section

Effets — 2 jetons

JetonValeurRôleVariable CSS
transitioncubic-bezier(.22,.61,.36,1)Courbe d'animation--f-transition
rayon_bouton999pxArrondi des boutons--f-rayon_bouton
§4

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é
SeuilS'applique àCritère
4,5:1texte courantWCAG 1.4.3 — AA
3:1texte ≥ 24 px, ou ≥ 18,66 px en grasWCAG 1.4.3 — AA
3:1éléments d'interface, graphiquesWCAG 1.4.11 — AA
7:1texte courantWCAG 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.

§5

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é :

  1. Plafond en em — sans effet : em se résout contre la police de l'élément lui-même.
  2. max(clamp(…), rem) — sans effet non plus. La mesure a révélé pourquoi : 11vw valait déjà 158 px, sous le plafond de 152 px. C'était vw le limiteur, pas le plafond.
  3. Mélanger rem et vw dans la composante fluideclamp(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).")
§6

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.

ApprocheCe qu'on écritCe 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