EXPERT 02 — DESIGN SYSTEM TERO GROUP : TOKENS, COMPOSANTS, GABARITS
Livrable : architecture complète du design system du groupe Tero — trois couches de tokens,
mécanisme de déclinaison par sous-marque, inventaire des composants, gabarits de pages,
patterns transverses, gouvernance.
Auteur : expert 02 — design system architect / UX-UI lead.
Date : 15 octobre 2026. Périmètre d'écriture : ce fichier uniquement.
Statut : proposition à intégrer par le lead. Rien ici n'est un fait de marque nouveau.
0. Méthode, sources et statut des preuves
| Étiquette | Signification | Ce que ça autorise |
|---|---|---|
| [MESURE] | Valeur relevée dans un fichier du dépôt ou calculée à partir de valeurs relevées | On la reproduit telle quelle |
| [REGLE] | Décision du design system (nommage, échelle, mécanisme) | On l'applique, on peut la contester en revue |
| [A VALIDER CLIENT] | Arbitrage de marque qui n'appartient pas à l'équipe technique | On ne l'invente pas : on l'isole et on attend |
Sources lues pour ce document :
| Source | Ce qui en a été tiré |
|---|---|
docs/AUDIT-TERO-BE-2026.md (via le brief de tâche) | Faits mesurés : 536 CSS, 9,1 Mo, 6 291 occurrences, 164 hex, 30 tailles, 15+ rayons, conteneur 1 100 px, CTA, contrastes |
.open-design/plugins/tero-group-design/craft/tero-group-rules.md (646 l.) | Sections 2, 3, 4, 6, 7, 8, 9, 10, 11, 17, 18, 20 lues intégralement |
…/design-systems/tero-group/tokens.css (88 l.) | Couche actuelle : primitifs + un mapping de contrat à 6 entrées |
…/design-systems/tero-group/design-tokens.json (244 l.) | Couche machine actuelle |
…/design-systems/tero-group/DESIGN.md (153 l.) | Doctrine, rampe, anti-patterns |
…/manifest.json, …/open-design.json | Inventaire de marques, capacités, fichiers déclarés |
_audit/logo-tero-2025.svg, _audit/path-geometry.json | Géométrie exacte des 5 tracés du mot-symbole |
OUTDATED/tero.be/wp-content/uploads/**/logo*.svg (12 fichiers Tero) + 2 vectoriels du projet padel | Variantes officielles : viewBox, tracés, couleurs, structure |
_ref/teropadelclub/** | Le modèle abouti : tokens 3 couches réels, polices, mouvement, budgets, 15 pièges |
Totaux mesurés dans ce document : 25 écarts ou manques relevés dans les fichiers du lead
(§ 1), 1 matrice de contrastes recalculée (§ 3.7), 3 000+ lignes de spécification exploitables.
---
1. CHALLENGE DE L'EXISTANT — 25 constats mesurés
Trié par gravité d'impact sur l'intégration. Chaque constat est vérifiable dans le fichier cité.
1.1 Erreurs de fait ou de marque (à corriger avant toute diffusion)
C1 — Le mot-symbole est écrit en capitales dans toute la documentation. [MESURE]
- Écrit :
DESIGN.mdL137 («TEROdans le logo,Terodans la phrase. Toujours. »),
L605 de tero-group-rules.md (checklist), design-tokens.json L220
("wordmarkCase": "TERO"), tero-group-rules.md § 4.3 L276.
- Mesuré : le fichier de référence
_audit/logo-tero-2025.svgporteviewBox="0 0 100 40"
et 5 tracés dont la géométrie exacte est connue (_audit/path-geometry.json) : trois
glyphes de largeur courante (x 0→16,49 ; 21,45→47,36 ; 53,72→67,79) et, à droite, un
couple anneau (x 73,45→100,006 ; y 0,25→26,74) + barre (y 32,55→39,04). Soit 4 lettres
plus une barre sous la 4ᵉ — la lecture la plus simple est t e r o.
- Ce que la mesure ne peut pas dire : la casse ne se lit pas dans les données de tracé.
Elle repose sur l'arbitrage du client reporté dans le brief (mot-symbole minuscule tero).
[A VALIDER CLIENT — confirmation écrite] : la charte doit citer cette décision, pas la déduire.
- Impact : l'erreur est propagée dans 4 fichiers et dans la checklist de livraison ; un
intégrateur qui suit la checklist écrit TERO dans le HTML du logo.
- Correction : partout,
tero(mot-symbole, minuscules) ·Tero(graphie rédactionnelle,
dans une phrase) · Tero Group (entité) · TERO : jamais, sauf si le client impose
une version capitales qu'aucun fichier du dépôt ne porte aujourd'hui.
C2 — TERO_MONOGRAM-1.png est décrit comme « un anneau abstrait » ; c'est le glyphe o isolé. [MESURE]
- Écrit :
tero-group-rules.md§ 4.1 L262 et piège n° 6 L582 (« anneau noir 1 272 × 1 861 »),
DESIGN.md L142 (« la vignette anneau utilisée comme logo »), design-tokens.json L242.
- Mesuré (calcul fait dans ce document) : dans
_audit/path-geometry.json, le 5ᵉ tracé a
pour boîte x 73,453 → 100,006 ; y 0,253 → 39,044, soit 26,553 × 38,790 unités →
rapport 0,6845. Le PNG _audit/monogram-tero-2026.png fait 1 272 × 1 861 →
rapport 0,6835. Échelle : 47,9 et 48,0 px par unité SVG. C'est le même dessin,
rastérisé à ×48 : le « monogramme » est le glyphe o (anneau + barre) du mot-symbole,
pas un anneau décoratif.
- Impact : la règle « ne jamais utiliser la vignette » interdit un actif de marque légitime
sans dire pourquoi ni par quoi le remplacer ; le vrai défaut (usage comme Organization.logo
en JSON-LD) se noie dans une interdiction trop large.
- Correction : reformuler en deux règles distinctes — (a) [MESURE] le monogramme est le
glyphe o du mot-symbole, ratio 0,684, non carré ; (b) [REGLE] il ne remplace jamais le
mot-symbole dans un en-tête ou dans Organization.logo ; (c) [A VALIDER CLIENT] son usage
comme icône d'application / favicon exige un carré qui n'existe pas encore dans les
fichiers — à faire produire par le studio, jamais par un agent.
C3 — « Almarose s'arrête à 700 » est faux : la graisse 900 existe. [MESURE]
- Écrit :
tero-group-rules.md§ 3.1 L190 et § 3.3 L221,DESIGN.mdL69-70,
design-tokens.json L86 (« Aucune graisse 800/900 : Almarose s'arrete a 700 »), et le
SKILL.md du plugin (« graisse 800/900 sur Almarose (faux gras : la famille s'arrête à 700) »).
- Mesuré :
_ref/teropadelclub/public/fonts/contientalmarose-900.woff2, chargé en
font-weight: 900 dans src/styles/global.css L38 ; _ref/…/DESIGN.md L57 décrit
« Almarose (Regular/Medium/Semibold/Bold/Black) ». La graisse **900 existe donc
physiquement** dans le jeu de la marque.
- Contradiction interne au modèle de référence :
_ref/…/DESIGN.mdL59 impose
« h1 900 · h2 800 » alors qu'aucun fichier 800 n'existe : ce h2 800 est un faux
gras synthétique — exactement le défaut que la charte interdit.
- Impact : la charte du groupe se prive d'une graisse réelle (le 900) et laisse passer la
seule graisse réellement synthétique (le 800) par le modèle censé être la référence.
- Correction : la règle opposable devient « **une graisse n'est autorisée que si son fichier
.woff2 existe » ; le jeu disponible est 400/500/600/700/900 ; le 800 est interdit**
(aucun fichier) ; toute graisse intermédiaire est interdite. Corriger h2 800 dans le modèle
padel. [A VALIDER CLIENT] : le groupe charge-t-il Almarose Black (fichier à récupérer) ou
reste-t-il sur 400→700 ?
C4 — Les variantes officielles du logo portent 5 couleurs hors palette. [MESURE]
- Écrit :
tero-group-rules.md§ 4.1 L253-262 liste 6 fichiers ;
§ 2.2 affirme que les 7 couleurs déclarées sont « les seules couleurs de marque déclarées ».
- Mesuré (inspection de 12 fichiers logo Tero du miroir WordPress et des 2 vectoriels du projet padel) :
| Fichier | viewBox | Tracés | Couleurs portées | Structure |
|---|---|---|---|---|
2023/12/logo-blue.svg | 385 × 170 | 0 path | #075F83 | raster masqué |
2023/12/logo-green.svg | 385 × 170 | 0 path | #068973 | raster masqué |
2023/12/logo_yellow.svg | 385 × 170 | 0 path | #F4C567 | raster masqué |
2023/12/logo-tero-white.svg | 170 × 75 | 0 path | white | raster masqué |
2023/12/logo-footer.svg | 144 × 63 | 0 path | (masque) | raster masqué |
2023/12/logo-hero.svg | 128 × 196 | 2 | #068973 + blanc | vectoriel |
2023/12/logo-venues-hero.svg | 128 × 196 | 3 | #CEA4A4 + blanc | vectoriel |
2023/12/logo-activities-hero.svg | 128 × 196 | 3 | #075F83 + blanc | vectoriel |
2023/12/logo-contact.svg | 128 × 196 | 3 | #F4C567 + blanc | vectoriel |
2023/12/logo-header.svg | 28 × 43 | 1 | #292929 | vectoriel |
2025/01/logo-header.svg | 28 × 43 | 1 | #292929 | vectoriel |
2024/08/logo.svg | 52 × 23 | 1 | (héritée) | vectoriel |
_ref/…/logo-tero.svg | 3 421 × 1 725 | 4 | #FFFFFF | vectoriel |
_ref/…/logo-tero-dark.svg | 3 421 × 1 725 | 4 | #0D1F30 | vectoriel |
- Impact : 5 valeurs non déclarées (
#075F83,#068973,#F4C567,#CEA4A4,
#292929) circulent dans des fichiers officiels de marque, alors que la palette fermée
n'en compte que 7 + 2 neutres. Trois d'entre elles sont des quasi-doublons de couleurs
déclarées (#075F83 ≈ pétrole #004B69 ; #068973 ≈ teal #007864 ; #F4C567 ≈ sable
#FAD282 ; #CEA4A4 ≈ rose poudré #D7B4B4) : c'est exactement la dérive à 164 valeurs
que l'audit condamne, mais dans les fichiers de marque eux-mêmes.
- Correction : (a) ajouter ces 5 valeurs à la liste des dérives interdites en § 2.5 ;
(b) remplacer « les 7 couleurs déclarées sont les seules » par « **les 7 couleurs déclarées
sont les seules** ; les fichiers logo historiques en portent 5 autres, à rééditer par le
studio » ; (c) [A VALIDER CLIENT] : quelle déclinaison couleur du logo est officielle —
noir, blanc, navy #0D1F30, ou aucune (mot-symbole noir uniquement) ?
C5 — Le « logo officiel » a deux géométries incompatibles dans le dépôt. [MESURE]
_audit/logo-tero-2025.svg:viewBox100 × 40, rapport 2,500, 5 tracés,
contenu qui remplit exactement le viewBox (x 0 → 100,006).
_ref/teropadelclub/public/logo-tero.svg:viewBox3 421 × 1 725, rapport 1,983,
4 tracés. Écart de 26 % sur la largeur relative : les deux fichiers ne peuvent pas être
la même composition.
- Impact : une charte qui dit « on utilise les fichiers officiels tels quels » sans dire
lequel ne tranche rien ; toute maquette est invalidée si la référence change.
- Correction : [A VALIDER CLIENT] le lead doit faire désigner un fichier de référence
(recommandation : le vectoriel 5 tracés, fill unique donc recolorable, car c'est lui qui
porte la barre sous le o décrite par le client), puis figer son viewBox et son rapport
dans design-tokens.json (brand.logo.aspectRatio).
C6 — 5 des 6 « fichiers officiels » ne sont pas des vecteurs : ce sont des rasters masqués. [MESURE]
- Écrit :
tero-group-rules.md§ 4.2.2 (« Aucun SVG recréé à la main »), § 4.1 les présente
comme des SVG de référence.
- Mesuré :
logo-blue.svg,logo-green.svg,logo_yellow.svg,logo-tero-white.svg
et logo-footer.svg ont 0 tracé : ils contiennent un <pattern id="pattern0"> qui
référence une <image> PNG base64 de 533 × 237 px, utilisée comme masque alpha
(mask-type:alpha), au-dessus d'un <rect> de couleur unie.
- Conséquences mesurables : (a) le contour du logo provient d'un masque 533 × 237 :
au-delà de ~533 px de large, le rendu est interpolé ; (b) ces fichiers pèsent ~2 034 octets
de base64 pour un logo qui devrait faire < 1 Ko en vectoriel ; (c) ils introduisent 4 des
5 couleurs de C4.
- Correction : (a)
tero-group-rules.md§ 4.1 : marquer ces 5 fichiers
« rasters masqués — ne pas utiliser en grand format, à rééditer en vectoriel » ;
(b) interdire le rendu du logo au-delà de 533 px de large tant que la réédition n'est pas
faite ; (c) [A VALIDER CLIENT] le studio réédite 3 variantes vectorielles (noir, blanc, une
déclinaison pilier) à fill unique.
C7 — Le manifeste du design system invente deux entités et en omet cinq. [MESURE]
manifest.jsonL36-51scope.subBrandsliste Tero Lodges et River Woods Beach Club,
absents de la structure du groupe donnée par le client (3 piliers : Hospitality, Leisure,
Sport Events + marques citées).
- Il omet
immorun.lu,bouwrun.be,buildandrun.be,12kmanderlecht.be,
terotrail.be (cité) — et Bistro 17/Knokke Out Zoute sont cités sans leur pilier
(« Bistro 17 », « Knokke Out »).
- Impact : le registre des marques est la clé primaire des thèmes (§ 4) et du bandeau
d'écosystème (§ 5.16). Une clé inventée produit une page fantôme ; une clé manquante produit
une marque sans rattachement — le piège n° 7 de la charte.
- Correction : une seule source de vérité machine :
content/sources/tero-brands.json (clé, nom officiel, casse exacte, pilier, domaine,
couleur d'accent éventuelle, statut), dérivée de content/sources/tero-group-facts.md.
manifest.json et les thèmes la lisent, ils ne la recopient pas. [A VALIDER CLIENT] :
Tero Lodges et River Woods Beach Club existent-ils ?
1.2 Erreurs d'accessibilité introduites par les tokens actuels (3 défauts prouvés)
C8 — --tero-muted #B9B9B9 est affecté aux libellés et placeholders : 1,96:1. [MESURE]
- Écrit :
tokens.cssL14 (« libellés, placeholders »), § 2.1 L129 (« texte désactivé,
libellés de champ »).
- Mesuré :
#B9B9B9sur blanc = 1,96:1, sur#F6F4F4= 1,79:1. Un placeholder
est du texte : WCAG 2.1 SC 1.4.3 exige 4,5:1. La charte elle-même écrit « jamais en texte
sur blanc » (§ 2.4 L171) et l'affecte pourtant aux libellés de champ.
- Collision de nommage : le contrat OpenDesign mappe
--muted: var(--tero-ink-soft)
(#323232) tandis que le primitif --tero-muted vaut #B9B9B9. **Le même mot muted
désigne deux couleurs opposées** (12,82:1 et 1,96:1).
- Correction :
--tero-mutedne porte plus de texte (réservé : traits désactivés,
icônes inactives sur fond sombre) ; libellés, placeholders et texte secondaire →
--ui-fg-muted = #323232 (12,82:1 sur blanc, 11,70:1 sur #F6F4F4, mesuré § 3.7).
Renommer le primitif --tero-muted en --tero-disabled pour supprimer la collision.
C9 — L'anneau de focus teal échoue sur la bande profonde (2,97:1). [MESURE]
- Écrit :
tokens.cssL83--focus-ring: 3px solid var(--tero-teal); § 7.1 L372 impose
ce même anneau partout.
- Mesuré (calcul § 3.7) : teal
#007864sur#212121= 2,97:1 — sous le seuil de
3:1 de WCAG 2.1 SC 1.4.11 (contraste des composants d'interface), et ce d'autant plus
que la bande profonde est explicitement autorisée (#212121 = --tero-ink-deep).
Sur un aplat teal, l'anneau teal tombe à 1,00:1 : il disparaît.
- Correction : l'anneau devient dépendant du champ (§ 4.4) :
light-a/light-b → #007864 (5,43:1) · deep → #FAD282 (11,20:1 sur #212121, mesuré)
· plus un halo de séparation 0 0 0 2px de la couleur du fond, pour que l'anneau reste
visible sur tout aplat de marque (teal, terracotta, sable).
C10 — Aucune couleur d'erreur, de succès ni d'avertissement n'existe. [MESURE]
- Écrit : § 7.4 L390 impose des états « erreur » et « chargement » ; § 10 L441 impose
« les erreurs sont annoncées et explicites ».
- Mesuré : la palette ne contient aucun token sémantique d'état (ni
error, ni
success, ni warning, ni info). Un intégrateur qui suit la charte n'a **aucune valeur
autorisée** pour colorer un message d'erreur : il prendra #B00000, qui est dans la liste
des dérives interdites (§ 2.5 L177).
- Correction [REGLE] :
--ui-danger= terracotta#B93815(5,77:1 sur blanc, 5,27:1
sur #F6F4F4, mesurés) · --ui-success = teal #007864 (5,43:1) ·
--ui-info = pétrole #004B69 (9,50:1) · --ui-warning = pas de couleur de texte :
sable #FAD282 (1,44:1) est inutilisable en texte, et une 8ᵉ couleur serait une dérive →
l'avertissement est icône + texte encre, jamais du jaune sur blanc. [A VALIDER CLIENT]
pour la couleur d'erreur (terracotta est un choix, pas une mesure).
C11 — Le filet #D9D9D9 est utilisé comme bordure de champ. [MESURE]
- Écrit :
tokens.cssL31-33 :--border: var(--tero-line)dans le contrat ;DESIGN.md
§ 6 et design-tokens.json contract.border = #D9D9D9.
- Mesuré :
#D9D9D9sur blanc = 1,41:1 ; sur#F6F4F4= 1,29:1. Un filet
décoratif est hors du périmètre de 1.4.11, mais la **limite visible d'un champ de
formulaire est un composant d'interface : 3:1 minimum**.
- Correction : deux tokens distincts —
--ui-border=#D9D9D9(**filets décoratifs
uniquement) et --ui-border-strong = #767676 (4,54:1 sur blanc, 4,15:1** sur
`#F6F4F4**, mesurés) pour toute bordure de champ, case à cocher, sélecteur. Le contrat
OpenDesign --border pointe sur --ui-border (compatibilité) : les **formulaires ne
l'utilisent jamais**.
1.3 Erreurs d'étiquetage [MESURE] vs [REGLE]
C12 — « Trois rayons » est étiqueté [MESURÉ] alors que c'est une règle. [MESURE]
DESIGN.mdL99 : « Trois rayons, pas quinze : … L'existant en compte 15+. [MESURÉ] »
Le [MESURÉ] porte sur les 15+ (vrai : radiusMeasuredDistinct: 15), pas sur « 3 » qui est
un choix. § 7.2 confirme la règle.
- Aggravant : le rayon retenu pour les médias est
12px, valeur **absente des rayons
dominants mesurés** (7 px ×570, 25 px ×225, 20 px ×188, 40 px ×157, 50 px ×116, 30 px ×99,
100 px ×98, 47 px ×83, 32 px ×74, 38 px ×70). Si la doctrine est « on ne s'écarte pas du
mesuré », alors --radius-card devrait valoir 20 px (188 occurrences) ou l'on assume
que 12 px est une règle nouvelle.
- Correction : réétiqueter
[RÈGLE, à partir de [MESURÉ] 15 rayons distincts]et justifier
12 px comme choix (ou passer à 20 px pour coller au mesuré dominant).
C13 — Le rayon du CTA est attribué à la mesure alors que la mesure dit 38 px. [MESURE]
- § 7.1 L367 : « fond noir
#000000(référence mesurée du CTA du site) … rayon pilule » ;
design-tokens.json components.buttonPrimary ne porte aucune provenance.
- L'audit mesure 38 px de rayon ; le piège n° 4 dénonce « une pilule à 38 px recopiée à la
main ». La valeur 999 px est une règle, pas la mesure.
- Correction : écrire « mesure : pilule noire à 38 px de rayon — **on ne reproduit pas
38 px** ; règle : --ui-radius-pill = 999 px ». Une règle qui corrige une mesure doit le
dire, sinon la mesure devient indiscutable.
C14 — La rampe typographique a deux définitions incompatibles. [MESURE]
DESIGN.mdL76-85 : 8 paliers en fourchettes (« 13-14 px », « 15-16 px », « 20-25 px »,
« 30-32 px », « 40-50 px », « 60-80 px »).
tokens.cssL48-59 : 12 paliers scalaires (--text-label→--text-display-l),
dont --text-body-l (16 px) et --text-title-s-l (25 px) qui n'existent pas dans
DESIGN.md.
- Impact : un token doit être scalaire. Une fourchette de 15 à 16 px réintroduit
exactement l'improvisation au pixel condamnée par § 3.2 (30 tailles distinctes) : l'intégrateur
choisit « au feeling » dans la fourchette.
- Correction : § 3.2 et
DESIGN.md§ 3 ne publient qu'une rampe : les 12 paliers
scalaires de tokens.css, et une note « bornes = tailles mesurées, interpolation fluide
= règle ». Supprimer toutes les fourchettes.
C15 — « Aucun texte sous 12 px » contredit le modèle de référence (11,2 px). [MESURE]
- § 8.4 L417 : « Aucun texte sous 12 px » ;
_ref/…/DESIGN.mdborne le label à 11,2 px
minimum (clamp 0.7rem = 11,2 px).
- Correction : trancher à 12 px plancher pour le groupe (la règle groupe gagne) et
aligner le modèle padel ; le label groupe devient clamp(12px → 13px) (§ 3.4).
1.4 Interdiction trop large qui produit des erreurs
C16 — « Jamais de teal sur fond noir » ne distingue pas le texte de l'aplat. [MESURE]
- § 2.3.5 L153-154 autorise le teal comme accent de bande profonde ; § 7.3 L385 le réautorise
puis L386 l'interdit en texte. Deux règles opposées dans la même section.
- Mesuré : teal en texte sur
#212121= 2,97:1 (échec) · teal en texte sur
#000000 = 3,87:1 (échec AA hors grand texte) · blanc sur aplat teal = 5,43:1 (AA, valide) ·
bord d'aplat teal sur #000000 = 3,87:1 (≥ 3:1, non-texte, valide).
- Correction : écrire la règle en trois lignes : sur bande profonde, (a) **accent
interactif = sable #FAD282 (11,20:1 sur #212121) ; (b) un aplat** teal est autorisé
si son texte est blanc ; (c) jamais de texte teal.
1.5 Manques structurels du design system (le cœur du sujet)
C17 — Il n'y a pas de design system : il y a une couche de primitifs. [MESURE]
tokens.css(88 lignes) = primitifs + 6 alias de contrat (--bg,--surface,--fg,
--muted, --border, --accent). design-tokens.json components = 4 entrées
(buttonPrimary, buttonSecondary, focus, deepBand).
- Aucun token de composant ne couvre : carte, formulaire, navigation, onglets, accordéon,
tableau, pagination, filtre, galerie, notification, fil d'Ariane, bandeau de marques,
état de chargement.
- Conséquence : chaque composant réinvente ses valeurs dans la page — le mécanisme même de
la dérive à 536 feuilles CSS.
- Correction : les trois couches de § 3, dont une couche de composants de ~120 tokens (§ 3.6).
C18 — Aucun token de mouvement, alors que § 9 impose des durées. [MESURE]
- § 9 L427 : « Durées courtes et intentionnelles : 150 à 250 ms, courbe stable » ; les
composants n'ont aucun token pour l'exprimer. Le modèle de référence, lui, interdit
explicitement les valeurs écrites à la main (« Aucun ease écrit à la main dans un
composant », 3 courbes maximum) et fournit 3 courbes + 6 durées.
- Correction : § 3.7, couche 1 — reprise à l'identique des courbes et durées du modèle
de référence (cubic-bezier(.23,1,.32,1), (.77,0,.175,1), (.32,.72,0,1) ;
140/200/240/380/680 ms) pour garantir la cohérence entre sites du groupe.
C19 — L'échelle d'espacement n'est pas une échelle. [MESURE]
tokens.cssL69-74 :12 · 24 · 48 · 72 · 96 · 144. Le modèle de référence impose une
échelle 8 pt : 4·8·12·16·24·32·48·64·96·128·160·192, avec la règle « **tout espacement
hors échelle est un bug** ».
- Mesuré : la rampe groupe n'a ni 4, ni 8, ni 16, ni 32, ni 64, ni 128, ni 192 ;
12 n'est pas un multiple de 8 → l'écart interne (padding de bouton, gap de grille de cartes)
n'a aucun token disponible. C'est la raison structurelle pour laquelle l'audit mesure
15+ rayons et 30 tailles : les mêmes besoins n'ont pas de valeur commune.
- Correction : § 3.4 — échelle 8 pt complète, la rampe verticale de section restant
48/72/96/144 (règle groupe conservée comme 4 profils de rythme : § 7.2).
C20 — Pas de clamp() : la rampe est figée, donc des media queries typographiques. [MESURE]
tokens.cssL48-59 : 12remfixes. Le modèle de référence : « 8 à 10 paliers fluides,
tous en clamp(). Jamais de palier redéfini en media query. »
- Impact : sans fluidité, chaque composant invente son propre palier par point de rupture —
c'est le mécanisme qui produit les 30 tailles distinctes mesurées sur tero.be.
- Correction : § 3.4, paliers fluides dont les **bornes sont exactement les tailles
mesurées** (12→13, 13→14, 14→15, 15→16, 17→18, 20→25, 30→32, 40→50, 60→80 px) : rien n'est
inventé, on rend la rampe responsive.
C21 — Pas de police de repli à métriques ajustées : le CLS est garanti. [MESURE]
tokens.cssL37 :--font-display: 'Almarose', ui-sans-serif, system-ui, …— aucune
famille de repli à métriques corrigées.
- Mesuré dans le modèle de référence : Almarose fait 85,08 % de la largeur d'Arial
(813 px contre 956 px sur la même chaîne à 100 px) ; sans size-adjust: 85.08%, le CLS de
/terrains/ passe de 0 → 0,021 (police bloquée) et un CLS de 0,0334 a été mesuré
par changement de graisse (900 préchargé après peinture).
- Correction : § 3.4 — déclarer
@font-face 'Almarose Fallback'avec
size-adjust: 85.08%, ascent-override: 90.5%, descent-override: 21.2%,
line-gap-override: 3.3% et l'insérer avant les polices système dans --font-display.
C22 — Aucun token de champ (fond/section), donc les contrastes dépendent de la page. [MESURE]
tokens.cssne connaît qu'un jeu de couleurs. OrDESIGN.mdautorise trois fonds
(blanc, #F6F4F4, bande profonde #000000/#212121). Le modèle de référence a résolu
ce point : tokens --field-light-a-bg, --field-light-b-bg, --field-deep-bg, et un
--trame-plaque re-tokenisé par champ.
- Conséquence mesurée : c'est exactement ce qui produit C9 (focus teal illisible sur fond
sombre) et, dans le projet padel, la dette documentée « un contraste peut passer en desktop
et échouer en mobile ».
- Correction : § 4.4 — quatre champs (
light-a,light-b,deep,accent) qui
redéfinissent la couche sémantique. Le contraste devient vérifiable par champ, pas par page.
C23 — Le conteneur du groupe (1 100 px) contredit le conteneur de la référence (1 344 px). [MESURE]
- § 8.2 L405 : conteneur 1 100 px [MESURÉ] ·
_ref/…/DESIGN.md§ 4 : 1 344 px (84 rem). - Correction : [A VALIDER CLIENT] — le groupe reste-t-il à sa largeur mesurée (1 100 px,
12 colonnes, gouttière 24 px) ou aligne-t-il toutes les marques sur 1 344 px ?
Le token --ui-container est verrouillé (§ 4.2) : la réponse vaut pour tout le groupe.
C24 — design-tokens.json classe muted-on-white à la fois en « aplatOnly » et en « forbidden ».
- L202 (
aplatOnly.muted-on-white: 1.96:1) et L209 (forbidden.muted-on-white: 1.96:1).
#B9B9B9 n'est pas un aplat : c'est un gris de texte désactivé. Un même couple dans deux
catégories opposées rend le fichier machine incohérent pour un validateur.
- Correction : supprimer de
aplatOnly, garder enforbidden(asTextOnLight) et
ajouter allowed.disabledSurfaceOnLight pour l'usage réel.
C25 — Ni forced-colors, ni prefers-contrast, ni prefers-reduced-motion au niveau des tokens.
- § 9 et § 10 imposent le respect de
prefers-reduced-motionet un focus jamais supprimé, mais
aucun token ne les porte : chaque composant devra s'en souvenir.
- Mesure du problème : sur 25 composants prévus (§ 5), 25 devraient répéter la même
@media. Ce qui est répété 25 fois sera oublié 3 fois.
- Correction : § 6.6 — trois blocs globaux qui neutralisent les durées, durcissent les
filets et remplacent l'anneau par Highlight : les composants n'ont rien à faire.
---
2. ARCHITECTURE CIBLE — vue d'ensemble
2.1 Les cinq fichiers et leur ordre
| Ordre | Fichier | Contenu | Couche CSS | Surchargeable par une marque |
|---|---|---|---|---|
| 1 | tokens/01-primitives.css | Couleurs, rampe, polices, espacements, rayons, durées, z-index | @layer tero.primitives | Jamais |
| 2 | tokens/02-semantique.css | --ui-* (rôles) + les 4 champs + le contrat OpenDesign | @layer tero.semantique | Oui, par liste blanche |
| 3 | tokens/03-composants.css | --btn-, --card-, --field-, --nav-, --faq-*… | @layer tero.composants | Non (hérite du thème) |
| 4 | themes/<cle-marque>.css | Surcharges de couche 2 uniquement | @layer tero.theme | — |
| — | tokens/99-compat.css | Alias --tero-/--bg/--text- historiques | @layer tero.compat | Non — déprécié, retiré en v2.0 |
/* Point d'entrée unique — l'ordre de cascade ne dépend PAS de l'ordre des fichiers : */
@layer tero.primitives, tero.compat, tero.semantique, tero.theme, tero.composants, tero.pages;
@import url('tokens/01-primitives.css') layer(tero.primitives);
@import url('tokens/99-compat.css') layer(tero.compat);
@import url('tokens/02-semantique.css')layer(tero.semantique);
@import url('themes/tero-group.css') layer(tero.theme);
@import url('tokens/03-composants.css')layer(tero.composants);
Pourquoi @layer est non négociable [REGLE] : sans couches, une sous-marque qui pose
<link> après le socle gagne par ordre d'arrivée, et l'ordre change au gré du bundler.
Avec @layer, la priorité est une décision écrite, pas un effet de bord de build.
Support : Chrome/Edge 99+, Firefox 97+, Safari 15.4+ — les mêmes navigateurs que clamp() et
color-mix(), déjà utilisés par la référence.
2.2 Conventions de nommage [REGLE]
| Couche | Préfixe | Motif | Exemple | Interdit |
|---|---|---|---|---|
| 1 primitifs | --tero- | --tero-<famille>-<palier> | --tero-teal, --tero-fs-title-m, --tero-space-32 | Un primitif nommé par un usage (--tero-cta) |
| 2 sémantiques | --ui- | --ui-<rôle>[-<variante>] | --ui-fg-muted, --ui-border-strong, --ui-accent | Une couleur brute |
| 3 composants | --<composant>- | --<composant>-<partie>-<état> | --btn-bg-hover, --card-title-size | Un composant sans token (valeur en dur) |
| Compatibilité | --tero-* historique + --bg/--fg/… | inchangé | --bg, --text-body | Toute nouvelle écriture |
Règles de nommage opposables :
- Un token = une valeur scalaire. Jamais de fourchette (cf. C14), jamais
calc()dans un
token de composant (le calcul vit dans la CSS du composant).
- Anglais technique, français documentaire. Les tokens sont en anglais (
--ui-border,
--btn-bg) ; la documentation et les commentaires sont en français.
- Pas d'abréviation non documentée.
bg,fg,fs,lh,pxsont autorisés
(convention Tailwind 4) ; tout le reste s'écrit en entier.
- Le suffixe d'état vient en dernier :
--btn-bg-hover, jamais--btn-hover-bg. - Un token déprécié garde son nom 1 version avec la valeur de repli, jamais un renommage
silencieux.
2.3 Le pont Tailwind 4 @theme [REGLE]
Le socle du groupe doit se consommer comme celui qui existe déjà dans
_ref/teropadelclub/src/styles/global.css (@theme = primitifs, --field-/--rhythm- =
sémantiques, .card-/.title- = composants). Le pont est donc :
@theme {
/* Couleurs : alias de la couche sémantique, jamais un primitif */
--color-surface: var(--ui-surface);
--color-surface-alt: var(--ui-surface-alt);
--color-fg: var(--ui-fg);
--color-fg-muted: var(--ui-fg-muted);
--color-border: var(--ui-border);
--color-border-strong: var(--ui-border-strong);
--color-accent: var(--ui-accent);
--color-accent-on: var(--ui-accent-on);
--color-deep: var(--ui-deep);
--color-deep-fg: var(--ui-deep-fg);
--color-danger: var(--ui-danger);
/* Tailles : la convention Tailwind 4 --text-*--line-height est adoptée telle quelle */
--text-body: var(--tero-fs-body);
--text-body--line-height: 1.65;
/* … */
}
Cette convention (--text-<palier> + --text-<palier>--line-height) est déjà celle du
modèle de référence (global.css L120-143) : l'adopter évite deux vocabulaires pour la même
chose — cause directe de C14.
---
3. LES TROIS COUCHES DE TOKENS
3.1 Couche 1 — PRIMITIFS (verrouillée), tokens/01-primitives.css
Valeurs mesurées sauf mention [REGLE]. Commentaires = occurrences mesurées.
@layer tero.primitives {
:root {
/* ─── Neutres — 77 % des 6 291 occurrences de couleur mesurées ───────────── */
--tero-black: #000000; /* 1 269 occ. */
--tero-white: #ffffff; /* 1 443 occ. */
--tero-surface: #f6f4f4; /* 388 occ. */
--tero-line: #d9d9d9; /* 130 occ. — filets DÉCORATIFS uniquement (1,41:1) */
--tero-line-soft: #e5e6e5; /* filets très légers, fonds de champ */
--tero-line-strong:#767676; /* [REGLE] 4,54:1 sur blanc — bordure de champ (1.4.11) */
--tero-ink-soft: #323232; /* 243 occ. — 12,82:1 sur blanc */
--tero-ink-deep: #212121; /* 188 occ. — bande profonde alternative */
--tero-disabled: #b9b9b9; /* 142 occ. — 1,96:1 : DÉCORATIF / DÉSACTIVÉ, jamais du texte */
/* ─── Sept couleurs de marque DÉCLARÉES (2,0 % des occurrences) ──────────── */
--tero-teal: #007864; /* 47 occ. — 5,43:1 sur blanc */
--tero-petrol: #004b69; /* 21 occ. — 9,50:1 sur blanc */
--tero-terracotta: #b93815; /* 15 occ. — 5,77:1 sur blanc */
--tero-sand: #fad282; /* 16 occ. — 1,44:1 sur blanc : JAMAIS en texte */
--tero-rose: #d7b4b4; /* 12 occ. — 1,90:1 sur blanc : JAMAIS en texte */
--tero-cyan: #00b7ff; /* 15 occ. — 2,28:1 sur blanc : JAMAIS en texte */
--tero-pink: #ff1d58; /* 1 occ. — 3,77:1 sur blanc : JAMAIS en texte */
/* ─── Dérives mesurées : VALEURS DE LECTURE SEULE, jamais consommables ───── */
/* Conservées pour que les scripts puissent les RECONNAÎTRE et les SIGNALER. */
--tero-drift-sage: #97ab83; /* 620 occ. — 3e couleur du site, hors charte */
--tero-drift-bordeaux: #b00000;
--tero-drift-prune: #680224;
--tero-drift-prune-deep:#550d24;
--tero-drift-rust: #b8451f;
--tero-drift-olive: #8bb169;
--tero-drift-forest: #183f3c;
--tero-drift-bluegrey: #5e93a7;
--tero-drift-sagelight: #d9ead3;
--tero-drift-cream: #fff5e5;
--tero-drift-sandwarm: #f0e6d7;
--tero-drift-taupe: #dacec7;
--tero-drift-blueice: #d5e9f1;
--tero-drift-creampale:#f9f4ec;
--tero-drift-red: #ff4a4a;
/* + 5 dérives des fichiers LOGO, mesurées dans ce document (cf. § 1.1 C4) : */
--tero-drift-logo-blue: #075f83;
--tero-drift-logo-green: #068973;
--tero-drift-logo-gold: #f4c567;
--tero-drift-logo-mauve: #cea4a4;
--tero-drift-logo-header: #292929;
/* ─── Typographie ───────────────────────────────────────────────────────── */
--tero-font-display: 'Almarose', 'Almarose Fallback', ui-sans-serif, system-ui,
-apple-system, 'Segoe UI', Roboto, sans-serif;
--tero-font-script: 'WildYouth', var(--tero-font-display);
/* Graisses : une graisse n'est autorisée QUE si son .woff2 existe. */
--tero-weight-regular: 400;
--tero-weight-medium: 500;
--tero-weight-semibold: 600;
--tero-weight-bold: 700;
--tero-weight-black: 900; /* [MESURE] almarose-900.woff2 existe — cf. C3 */
/* 800 : INTERDIT — aucun fichier n'existe. Aucun token ne lui est attribué. */
/* Rampe : 9 paliers fluidifiés. BORNES = tailles dominantes MESURÉES sur tero.be. */
--tero-fs-label: clamp(0.75rem, 0.7292rem + 0.0926vw, 0.8125rem); /* 12 → 13 px / 160+455 occ. */
--tero-fs-meta: clamp(0.8125rem, 0.7917rem + 0.0926vw, 0.875rem); /* 13 → 14 px / 455+659 occ. */
--tero-fs-body-s: clamp(0.875rem, 0.8542rem + 0.0926vw, 0.9375rem); /* 14 → 15 px / 659 occ. */
--tero-fs-body: clamp(0.9375rem, 0.9167rem + 0.0926vw, 1rem); /* 15 → 16 px / 1 039 occ. */
--tero-fs-lead: clamp(1.0625rem, 1.0417rem + 0.0926vw, 1.125rem); /* 17 → 18 px / 523 occ. */
--tero-fs-title-s: clamp(1.25rem, 1.1458rem + 0.463vw, 1.5625rem); /* 20 → 25 px / 616+515 occ. */
--tero-fs-title-m: clamp(1.875rem, 1.8333rem + 0.1852vw, 2rem); /* 30 → 32 px / 282+170 occ. */
--tero-fs-title-l: clamp(2.5rem, 2.2917rem + 0.9259vw, 3.125rem); /* 40 → 50 px / 291+116 occ. */
--tero-fs-display: clamp(3.75rem, 3.3333rem + 1.8519vw, 5rem); /* 60 → 80 px / 129+ 75 occ. */
/* Interlignage et lettrage (règle groupe § 3.3) */
--tero-lh-display: 1.02;
--tero-lh-title: 1.08;
--tero-lh-title-m: 1.15;
--tero-lh-title-s: 1.25;
--tero-lh-lead: 1.60;
--tero-lh-body: 1.65;
--tero-lh-meta: 1.45;
--tero-lh-label: 1.20;
--tero-ls-title: -0.015em; /* bornes règle : -0.01 à -0.02em */
--tero-ls-display: -0.025em; /* [REGLE] grand affichage : valeur du modèle de référence */
--tero-ls-label: 0.18em;
--tero-measure-chapo: 58ch;
--tero-measure-body: 66ch; /* [REGLE] repère du modèle ; le groupe ne le fixait pas */
--tero-measure-faq: 62ch;
--tero-measure-side: 42ch;
/* ─── Espacement : échelle 8 pt COMPLÈTE [REGLE] ─────────────────────────── */
/* Le groupe ne publiait que 12/24/48/72/96/144 : 16, 32 et 64 manquaient. */
--tero-space-4: 0.25rem; /* 4 px */
--tero-space-8: 0.5rem; /* 8 px */
--tero-space-12: 0.75rem; /* 12 px */
--tero-space-16: 1rem; /* 16 px */
--tero-space-24: 1.5rem; /* 24 px */
--tero-space-32: 2rem; /* 32 px */
--tero-space-48: 3rem; /* 48 px */
--tero-space-64: 4rem; /* 64 px */
--tero-space-96: 6rem; /* 96 px */
--tero-space-128: 8rem; /* 128 px */
--tero-space-192: 12rem; /* 192 px */
/* Rythme vertical de section : 4 profils FLUIDES (bornes = règle groupe 48/72/96/144) */
--tero-rhythm-flush: clamp(2rem, 1.75rem + 1.1111vw, 2.75rem); /* 32 → 44 px */
--tero-rhythm-tight: clamp(2.75rem,2.3333rem+ 1.8519vw, 4rem); /* 44 → 64 px */
--tero-rhythm-normal: clamp(3.75rem,3rem + 3.3333vw, 6rem); /* 60 → 96 px */
--tero-rhythm-loose: clamp(4.5rem, 3.5rem + 4.4444vw, 7.5rem); /* 72 → 120 px */
--tero-space-entries: 2.5rem; /* entre deux entrées éditoriales (règle groupe) */
/* ─── Grille et conteneur ───────────────────────────────────────────────── */
--tero-container: 68.75rem; /* 1 100 px — MESURÉ (cf. C23 pour l'arbitrage) */
--tero-container-text: 47.5rem; /* 760 px [REGLE] colonne de lecture */
--tero-gutter: clamp(1rem, 0.8333rem + 0.7407vw, 1.5rem); /* 16 → 24 px */
--tero-grid-columns: 12;
/* Points de rupture de CONTRÔLE (règle groupe § 8.2) — déclaratifs, jamais une rampe typo */
--tero-bp-xs: 22.5rem; /* 360 px */
--tero-bp-sm: 24.375rem;/* 390 px */
--tero-bp-md: 48rem; /* 768 px */
--tero-bp-lg: 64rem; /* 1 024 px */
--tero-bp-xl: 90rem; /* 1 440 px */
/* ─── Rayons : TROIS valeurs [REGLE] ────────────────────────────────────── */
--tero-radius-media: 0.75rem; /* 12 px — choix (cf. C12 : le mesuré dominant est 20 px) */
--tero-radius-plate: 1.25rem; /* 20 px — plaque rare (188 occ. mesurées) */
--tero-radius-pill: 999px; /* piloté par la règle, la mesure était 38 px (cf. C13) */
/* ─── Filets, focus, cibles ─────────────────────────────────────────────── */
--tero-hairline: 1px;
--tero-focus-width: 3px;
--tero-focus-offset: 3px;
--tero-tap-target: 44px; /* règle maison ; plancher légal WCAG 2.2 : 24 × 24 px */
/* ─── Mouvement : 3 courbes, 6 durées [REGLE] — reprises de la référence ── */
--tero-ease-out-strong: cubic-bezier(0.23, 1, 0.32, 1);
--tero-ease-in-out-soft: cubic-bezier(0.77, 0, 0.175, 1);
--tero-ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
--tero-dur-fast: 140ms; /* feedback : couleur, opacité, soulignement */
--tero-dur-base: 200ms; /* survol, état de bouton */
--tero-dur-move: 240ms; /* tiroir, panneau, changement d'onglet */
--tero-dur-draw: 380ms; /* modale, menu mobile plein écran */
--tero-dur-slow: 680ms; /* révélation au défilement */
--tero-dur-marquee: 55s; /* boucle linéaire, seul usage de linear admis */
/* ─── Profondeur : une seule ombre autorisée [REGLE] ────────────────────── */
/* La charte interdit la « carte empilée » (fond + bordure + rayon + ombre) et les
« ombres lourdes » : il reste donc UNE ombre, réservée aux surfaces flottantes
(en-tête collant, menu, modale, notification), jamais à une carte de contenu. */
--tero-shadow-float: 0 1px 2px rgb(0 0 0 / 0.04), 0 12px 28px -14px rgb(0 0 0 / 0.14);
/* ─── Empilement : échelle nommée [REGLE] ───────────────────────────────── */
--tero-z-base: 0; --tero-z-sticky: 10; --tero-z-header: 20; --tero-z-dropdown: 30;
--tero-z-overlay: 40; --tero-z-drawer: 50; --tero-z-modal: 60; --tero-z-toast: 70;
}
}
Ce que la couche 1 ne contient pas, et pourquoi : aucun token nommé par un usage
(cf. C17), aucune couleur d'état (danger/success sont sémantiques, § 3.5), aucune
valeur de composant. Une sous-marque qui écrit --tero-* dans son fichier de thème est
rejetée par la porte de qualité § 8.4.
3.2 Les CHAMPS (field) — la brique qui garantit le contraste
Le contraste n'est pas une propriété d'une couleur, c'est une propriété d'un **couple
(texte, fond)**. La couche actuelle n'a qu'un seul jeu de couleurs pour trois fonds différents :
c'est la cause directe de C9 et C22. Le design system introduit donc quatre champs, exactement
comme le modèle de référence (--field-light-a-bg, --field-light-b-bg, --field-deep-bg).
| Champ | Fond | Usage | Attribution |
|---|---|---|---|
light-a | #FFFFFF | défaut : contenu, éditorial | data-field="light-a" ou rien |
light-b | #F6F4F4 | section secondaire, alternance | data-field="light-b" |
deep | #000000 ou #212121 | bande profonde, hero sombre, pied de page | data-field="deep" |
accent | --ui-accent | bandeau de chiffres, bloc d'action, encart marque | data-field="accent" |
Règle [REGLE] : un composant ne lit jamais un primitif de couleur. Il lit --ui-*.
Comme --ui- est redéfini par chaque champ, *une erreur de contraste devient impossible au
niveau du composant** : elle ne peut venir que d'une erreur de champ, détectable en une passe
(data-field manquant sur un bloc sombre = défaut bloquant, § 8.4).
@layer tero.semantique {
/* ── champ light-a (défaut) ───────────────────────────────────────────── */
:root, [data-field='light-a'] {
--ui-surface: var(--tero-white);
--ui-surface-alt: var(--tero-surface);
--ui-fg: var(--tero-black);
--ui-fg-muted: var(--tero-ink-soft); /* 12,82:1 sur blanc — LIBELLÉS, PLACEHOLDERS */
--ui-fg-subtle: #6b6b6b; /* [REGLE] 5,33:1 — légendes, mentions */
--ui-border: var(--tero-line); /* 1,41:1 — filets DÉCORATIFS seulement */
--ui-border-strong: var(--tero-line-strong); /* 4,54:1 — bordure de CHAMP (1.4.11) */
--ui-accent: var(--tero-teal);
--ui-accent-text: var(--tero-teal); /* 5,43:1 sur blanc */
--ui-accent-on: var(--tero-white); /* blanc sur aplat teal : 5,43:1 */
--ui-action-bg: var(--tero-black); /* CTA mesuré du site groupe : pilule noire */
--ui-action-bg-hover:var(--tero-ink-deep);
--ui-action-fg: var(--tero-white);
--ui-focus: var(--tero-teal); /* 5,43:1 sur blanc */
--ui-focus-halo: var(--tero-white);
--ui-danger: var(--tero-terracotta); /* 5,77:1 */
--ui-danger-bg: #fdf3ef; /* [REGLE] fond d'erreur, à valider en QA */
--ui-success: var(--tero-teal);
--ui-info: var(--tero-petrol);
--ui-selection-bg: var(--tero-sand);
--ui-selection-fg: var(--tero-black);
--ui-image-overlay: linear-gradient(180deg, rgb(0 0 0 / 0) 40%, rgb(0 0 0 / 0.62) 100%);
}
/* ── champ light-b : mêmes encres, surface changée ────────────────────── */
[data-field='light-b'] {
--ui-surface: var(--tero-surface);
--ui-surface-alt: var(--tero-white);
/* --ui-fg-muted #323232 sur #F6F4F4 = 11,70:1 : rien à changer */
/* --ui-border #D9D9D9 sur #F6F4F4 = 1,29:1 : filets décoratifs uniquement */
}
/* ── champ deep : TOUT est recomposé. C'est ici que le C9 est corrigé. ── */
[data-field='deep'] {
--ui-surface: var(--tero-black);
--ui-surface-alt: var(--tero-ink-deep);
--ui-fg: var(--tero-white); /* 21:1 */
--ui-fg-muted: #d4d4d4; /* [REGLE] 13,7:1 sur #000 — jamais #B9B9B9 */
--ui-fg-subtle: #a3a3a3; /* [REGLE] 8,6:1 sur #000 */
--ui-border: rgb(255 255 255 / 0.18); /* filet translucide, jamais #D9D9D9 */
--ui-border-strong: var(--tero-white);
/* ACCENT INTERACTIF DU FOND PROFOND : le sable. Le teal est illisible (2,97:1). */
--ui-accent: var(--tero-sand);
--ui-accent-text: var(--tero-sand); /* 14,61:1 sur #000 / 11,20:1 sur #212121 */
--ui-accent-on: var(--tero-black); /* noir sur sable : 14,61:1 */
--ui-action-bg: var(--tero-white);
--ui-action-bg-hover:var(--tero-sand);
--ui-action-fg: var(--tero-black);
--ui-focus: var(--tero-sand); /* 11,20:1 sur #212121 ≥ 3:1 exigé */
--ui-focus-halo: var(--tero-black);
--ui-danger: var(--tero-pink); /* 5,57:1 sur #000 (4,27:1 sur #212121 : grand texte) */
--ui-success: var(--tero-cyan); /* 9,22:1 sur #000 */
--ui-info: var(--tero-rose); /* 11,08:1 sur #000 */
}
/* ── champ accent : le fond EST l'accent du thème ─────────────────────── */
[data-field='accent'] {
--ui-surface: var(--ui-accent);
--ui-fg: var(--ui-accent-on);
--ui-fg-muted: var(--ui-accent-on);
--ui-border: color-mix(in srgb, var(--ui-accent-on) 25%, transparent);
--ui-focus: var(--ui-accent-on);
--ui-focus-halo: var(--ui-accent);
--ui-action-bg: var(--ui-accent-on);
--ui-action-fg: var(--ui-accent);
}
}
3.3 Couleur de texte secondaire : la vraie échelle d'encres [REGLE]
Le site n'a qu'un gris secondaire réel (#323232, 243 occ.). Le design system en publie
trois, tous calculés sur les deux fonds clairs :
| Token | Valeur | sur #FFFFFF | sur #F6F4F4 | Usage |
|---|---|---|---|---|
--ui-fg | #000000 | 21,00:1 | 19,17:1 | texte principal |
--ui-fg-muted | #323232 | 12,82:1 | 11,70:1 | texte secondaire, libellés, placeholders |
--ui-fg-subtle | #6B6B6B | 5,33:1 | 4,86:1 | légendes, mentions, numéros d'ordre |
--ui-disabled-fg | #767676 | 4,54:1 | 4,15:1 | état désactivé (le #B9B9B9 du site fait 1,96:1) |
--tero-disabled | #B9B9B9 | 1,96:1 | 1,79:1 | jamais du texte : traits, icônes inactives sur fond sombre |
C'est la correction de C8 : le placeholder n'est plus gris clair. Un placeholder doit rester
lisible (1.4.3 s'applique au texte, y compris les indices de saisie) ; il se distingue du texte
saisi par le style, pas par l'illisibilité.
3.4 Typographie : déclaration des polices et repli mesuré
@layer tero.primitives {
@font-face { font-family:'Almarose'; src:url('/fonts/almarose-400.woff2') format('woff2');
font-weight:400; font-style:normal; font-display:swap; }
@font-face { font-family:'Almarose'; src:url('/fonts/almarose-500.woff2') format('woff2');
font-weight:500; font-style:normal; font-display:swap; }
@font-face { font-family:'Almarose'; src:url('/fonts/almarose-600.woff2') format('woff2');
font-weight:600; font-style:normal; font-display:swap; }
@font-face { font-family:'Almarose'; src:url('/fonts/almarose-700.woff2') format('woff2');
font-weight:700; font-style:normal; font-display:swap; }
/* 900 : MESURÉ disponible (cf. C3). À charger UNIQUEMENT si le H1 du groupe l'utilise. */
@font-face { font-family:'Almarose'; src:url('/fonts/almarose-900.woff2') format('woff2');
font-weight:900; font-style:normal; font-display:swap; }
/* Repli à métriques ajustées — MESURÉ dans la référence : Almarose = 85,08 % d'Arial.
Sans lui, le CLS mesuré passe de 0 à 0,0334. */
@font-face {
font-family:'Almarose Fallback';
src: local('Arial'), local('Helvetica Neue'), local('Liberation Sans'), local('DejaVu Sans');
size-adjust: 85.08%;
ascent-override: 90.5%;
descent-override: 21.2%;
line-gap-override: 3.3%;
}
/* WildYouth : [A VALIDER CLIENT]. Tant que non arbitrée : déclarée, jamais chargée
(0 occurrence mesurée dans 536 feuilles). Le @font-face ne doit PAS être émis. */
}
Hiérarchie opposable (règle groupe § 3.3, complétée par les graisses réellement disponibles) :
| Niveau | Taille | Graisse | Interlignage | Interlettrage |
|---|---|---|---|---|
| Sur-titre | --tero-fs-label | 600, capitales | 1,20 | +0.18em |
| H1 | --tero-fs-display | 700 (900 si almarose-900 est livré) | 1,02 | -0.025em |
| H2 | --tero-fs-title-l | 600 | 1,08 | -0.015em |
| H3 | --tero-fs-title-m | 500-600 | 1,15 | -0.015em |
| H4 | --tero-fs-title-s | 500 | 1,25 | -0.01em |
| Chapô | --tero-fs-lead | 400 | 1,60 | 0 |
| Corps | --tero-fs-body | 400 | 1,65 | 0 |
| Emphase | hérite | 600 | — | — |
| Mention | --tero-fs-meta | 400 | 1,45 | 0 |
8 niveaux, 4 graisses autorisées (+900 sous condition), aucune autre combinaison. Le
h2 800 du modèle de référence est supprimé (C3).
3.5 Couche 2 — SÉMANTIQUE : le contrat de thème
En plus des tokens de champ (§ 3.2), la couche sémantique expose exactement les variables
qu'une sous-marque peut redéfinir. C'est la liste blanche de § 4.2. Rien d'autre.
@layer tero.semantique {
:root {
/* — LES 12 VARIABLES SURCHARGEABLES PAR UNE SOUS-MARQUE (liste blanche) — */
--ui-accent: <couleur>; /* aplat de marque */
--ui-accent-on: <white|black>; /* texte posé SUR l'accent — contraste ≥ 4,5:1 exigé */
--ui-accent-text: <couleur>; /* accent utilisé COMME TEXTE sur fond clair — ≥ 4,5:1 */
--ui-accent-hover: <couleur>; /* survol d'un aplat — ≥ 3:1 avec l'accent */
--ui-accent-soft: <couleur>; /* aplat très clair (encart) */
--ui-deep: <couleur>; /* fond de bande profonde de la marque */
--ui-deep-fg: <couleur>; /* texte sur la bande profonde — ≥ 4,5:1 */
--ui-focus: <couleur>; /* anneau — ≥ 3:1 sur tous les champs de la marque */
--ui-focus-halo: <couleur>;
--ui-brand-font: <famille>; /* RÉSERVÉ : 'Almarose' uniquement, cf. § 4.2 */
--ui-image-treatment: <filter|none>; /* traitement d'image (duotone p.ex.) */
--ui-decoration: <image|none>; /* motif de points, trame, filet signature */
/* — LE CONTRAT OPENDESIGN (compatibilité, jamais surchargé par une marque) — */
--bg: var(--ui-surface);
--surface: var(--ui-surface-alt);
--fg: var(--ui-fg);
--muted: var(--ui-fg-muted); /* CORRIGÉ : pointait sur un gris à 1,96:1 */
--border: var(--ui-border);
--accent: var(--ui-accent);
}
}
3.6 Couche 3 — COMPOSANTS : ~150 tokens
C'est la couche absente aujourd'hui (C17). Elle est dérivée des champs et des sémantiques,
donc automatiquement juste sur les quatre fonds.
@layer tero.composants {
:root {
/* ═══ BOUTON — 3 niveaux ═══════════════════════════════════════════════ */
--btn-min-h: var(--tero-tap-target); /* 44 px */
--btn-pad-block: var(--tero-space-12); /* 12 px — mesuré : .75rem */
--btn-pad-inline: var(--tero-space-24); /* 24 px — mesuré : 1.25rem */
--btn-gap: var(--tero-space-8);
--btn-radius: var(--tero-radius-pill);
--btn-font-size: var(--tero-fs-body-s); /* 14 px — mesuré : 0.875rem */
--btn-font-weight: var(--tero-weight-semibold); /* 600 — mesuré */
--btn-transition: background-color var(--tero-dur-fast) var(--tero-ease-out-strong),
color var(--tero-dur-fast) var(--tero-ease-out-strong),
border-color var(--tero-dur-fast) var(--tero-ease-out-strong);
--btn-1-bg: var(--ui-action-bg);
--btn-1-fg: var(--ui-action-fg);
--btn-1-bg-hover: var(--ui-action-bg-hover);
--btn-1-active-scale: 0.97;
--btn-2-bg: transparent;
--btn-2-border: 1px solid var(--ui-action-bg);
--btn-2-fg: var(--ui-action-bg);
--btn-2-bg-hover: var(--ui-action-bg);
--btn-2-fg-hover: var(--ui-action-fg);
--btn-3-fg: var(--ui-accent-text);
--btn-3-underline-gap: 0.25em;
--btn-disabled-bg: var(--ui-surface-alt);
--btn-disabled-fg: var(--ui-disabled-fg);
--btn-icon-size: 1.25em;
/* ═══ LIEN ═════════════════════════════════════════════════════════════ */
--link-fg: var(--ui-accent-text);
--link-underline: 1px;
--link-underline-offset:0.2em;
--link-underline-thickness-hover: 2px;
--link-inline-pad-block: 0.25rem; /* agrandit la cible sans pseudo-élément (vérifiable) */
/* ═══ CARTE D'ENTITÉ ═══════════════════════════════════════════════════ */
--card-gap: var(--tero-space-12);
--card-radius-media: var(--tero-radius-media);
--card-title-size: var(--tero-fs-title-s);
--card-title-weight: var(--tero-weight-semibold);
--card-title-lh: var(--tero-lh-title-s);
--card-meta-size: var(--tero-fs-meta);
--card-meta-fg: var(--ui-fg-muted);
--card-media-ratio: 3 / 2;
--card-media-ratio-wide:16 / 9;
--card-media-ratio-portrait: 4 / 5;
--card-separator: var(--tero-hairline) solid var(--ui-border);
--card-bg: transparent; /* JAMAIS de fond + bordure + rayon + ombre */
--card-shadow: none; /* la charte l'interdit : on le rend explicite */
--card-media-zoom: 1.03;
--card-media-transition:transform var(--tero-dur-slow) var(--tero-ease-out-strong);
/* ═══ GRILLE DE CARTES ═════════════════════════════════════════════════ */
--grid-gap: var(--tero-space-24);
--grid-min-card: 17.5rem; /* 280 px : seuil d'auto-fit */
--grid-columns-sm: 1;
--grid-columns-md: 2;
--grid-columns-lg: 3;
--grid-columns-xl: 4; /* [REGLE] jamais plus de 4 entités par rangée */
/* ═══ BANDEAU HERO ═════════════════════════════════════════════════════ */
--hero-min-h: 34rem;
--hero-min-h-full: 100svh; /* jamais 100vh — dette mesurée à 49 pages */
--hero-overlay: var(--ui-image-overlay);
--hero-title-size: var(--tero-fs-display);
--hero-content-max: 38rem;
--hero-pad-block: var(--tero-rhythm-loose);
--hero-scrim-min: 0.45; /* opacité minimale pour tenir 4,5:1 sur photo claire */
/* ═══ BANDE PROFONDE ═══════════════════════════════════════════════════ */
--band-pad-block: var(--tero-rhythm-normal);
--band-bg: var(--ui-surface); /* via data-field="deep" */
--band-fg: var(--ui-fg);
--band-accent: var(--ui-accent-text); /* sable par défaut : 11,20:1 sur #212121 */
--band-rule: var(--tero-hairline) solid var(--ui-border);
/* ═══ BANDE DE CHIFFRES ════════════════════════════════════════════════ */
--stat-size: var(--tero-fs-title-l);
--stat-weight: var(--tero-weight-bold);
--stat-variant: tabular-nums;
--stat-size-mobile: var(--tero-fs-title-m);
--stat-label-size: var(--tero-fs-meta);
--stat-label-fg: var(--ui-fg-muted);
--stat-separator: var(--tero-hairline) solid var(--ui-border);
--stat-accent: var(--tero-terracotta); /* terracotta = chiffres (règle groupe) */
/* ═══ PASSAGE CITABLE ══════════════════════════════════════════════════ */
--quote-size: var(--tero-fs-lead);
--quote-lh: 1.55;
--quote-measure: var(--tero-measure-chapo);
--quote-mark-size: 3rem; /* le guillemet est typographique, jamais une image */
--quote-source-size: var(--tero-fs-meta);
--quote-rule: var(--tero-hairline) solid var(--ui-border-strong);
/* ═══ FAQ / ACCORDÉON ══════════════════════════════════════════════════ */
--faq-question-size: var(--tero-fs-title-s);
--faq-question-weight: var(--tero-weight-medium);
--faq-answer-size: var(--tero-fs-body);
--faq-answer-measure: var(--tero-measure-faq);
--faq-rule: var(--tero-hairline) solid var(--ui-border);
--faq-pad-block: var(--tero-space-24);
--faq-marker-size: 1.25rem;
--faq-transition: grid-template-rows var(--tero-dur-base) var(--tero-ease-out-strong);
/* ═══ ONGLETS ══════════════════════════════════════════════════════════ */
--tabs-gap: var(--tero-space-4);
--tabs-pad-block: var(--tero-space-12);
--tabs-pad-inline: var(--tero-space-16);
--tabs-fg: var(--ui-fg-muted);
--tabs-fg-selected: var(--ui-fg);
--tabs-indicator: 2px solid var(--ui-accent);
--tabs-radius: var(--tero-radius-pill);
--tabs-transition: color var(--tero-dur-fast) var(--tero-ease-out-strong);
/* ═══ FIL D'ARIANE ═════════════════════════════════════════════════════ */
--crumb-size: var(--tero-fs-meta);
--crumb-fg: var(--ui-fg-muted);
--crumb-fg-current: var(--ui-fg);
--crumb-separator: '/';
--crumb-max-lines: 2; /* jamais tronqué — dette mesurée sur 5 largeurs */
/* ═══ EN-TÊTE ET NAVIGATION ════════════════════════════════════════════ */
--header-h: 4.5rem; /* 72 px — sert à scroll-padding ET scroll-margin */
--header-h-compact: 3.5rem;
--header-bg-sticky: color-mix(in srgb, var(--ui-surface) 88%, transparent);
--header-fallback-bg: var(--ui-surface); /* repli opaque obligatoire (pas de glassmorphism nu) */
--header-rule: var(--tero-hairline) solid var(--ui-border);
--nav-item-size: var(--tero-fs-body-s);
--nav-item-weight: var(--tero-weight-medium);
--nav-item-gap: var(--tero-space-24);
--nav-item-fg-current: var(--ui-fg);
--nav-item-indicator: 2px solid var(--ui-accent);
--mega-pad: var(--tero-space-32);
--mega-columns: 3;
--mega-column-min: 13.75rem; /* 220 px */
--mega-shadow: var(--tero-shadow-float);
--drawer-width: 22rem;
--drawer-bg: var(--ui-surface);
--drawer-transition: transform var(--tero-dur-draw) var(--tero-ease-drawer);
--overlay-bg: rgb(0 0 0 / 0.45);
/* ═══ PIED DE PAGE ═════════════════════════════════════════════════════ */
--footer-pad-block: var(--tero-rhythm-normal);
--footer-columns: 4;
--footer-link-fg: var(--ui-fg-muted);
--footer-link-fg-hover: var(--ui-fg);
--footer-legal-size: var(--tero-fs-meta);
--footer-signature-size:var(--tero-fs-body-s);
/* ═══ BANDEAU DES MARQUES DU GROUPE ════════════════════════════════════ */
--brandbar-gap: var(--tero-space-32);
--brandbar-logo-h: 2rem; /* hauteur optique unifiée, jamais la hauteur native */
--brandbar-logo-h-min: 1.75rem;
--brandbar-fg: var(--ui-fg-muted);
--brandbar-mono: 1; /* [REGLE] logos en monochrome encre ; couleur au survol */
/* ═══ CARROUSEL ════════════════════════════════════════════════════════ */
--carousel-gap: var(--tero-space-24);
--carousel-peek: 1.5rem; /* aperçu de la carte suivante : affordance de défilement */
--carousel-btn-size: 2.75rem;
--carousel-btn-bg: var(--ui-surface);
--carousel-btn-fg: var(--ui-fg);
--carousel-btn-shadow: var(--tero-shadow-float);
--carousel-dot-size: 0.5rem;
--carousel-dot-active: var(--ui-accent);
--carousel-snap: x mandatory;
--carousel-autoplay: 0; /* [REGLE] jamais d'autoplay : 2.2.2 (pause) + INP */
/* ═══ FORMULAIRE ═══════════════════════════════════════════════════════ */
--field-pad-block: var(--tero-space-12);
--field-pad-inline: var(--tero-space-16);
--field-radius: 0.5rem; /* [REGLE] 4e rayon : autorisé uniquement pour les champs */
--field-bg: var(--ui-surface);
--field-border: 1px solid var(--ui-border-strong); /* 4,54:1 — jamais #D9D9D9 */
--field-fg: var(--ui-fg);
--field-placeholder: var(--ui-fg-muted); /* CORRIGÉ (C8) */
--field-label-size: var(--tero-fs-body-s);
--field-label-weight: var(--tero-weight-medium);
--field-font-size-max: 1rem; /* 16 px min : anti-zoom iOS */
--field-hint-size: var(--tero-fs-meta);
--field-hint-fg: var(--ui-fg-muted);
--field-border-invalid: 2px solid var(--ui-danger);
--field-bg-invalid: var(--ui-danger-bg);
--field-checkbox-size: 1.25rem; /* + zone cliquable 44 px autour */
--field-consent-gap: var(--tero-space-12);
--field-error-gap: var(--tero-space-4);
/* ═══ NOTIFICATIONS ════════════════════════════════════════════════════ */
--toast-width: 22rem;
--toast-pad: var(--tero-space-16);
--toast-radius: 0.5rem;
--toast-bg: var(--ui-fg);
--toast-fg: var(--ui-surface);
--toast-shadow: var(--tero-shadow-float);
--toast-duration: 6000ms;
--toast-z: var(--tero-z-toast);
/* ═══ TABLEAUX ═════════════════════════════════════════════════════════ */
--table-cell-pad-block: var(--tero-space-12);
--table-cell-pad-inline:var(--tero-space-16);
--table-head-fg: var(--ui-fg-muted);
--table-head-size: var(--tero-fs-meta);
--table-rule: var(--tero-hairline) solid var(--ui-border);
--table-stripe-bg: var(--ui-surface-alt);
--table-caption-size: var(--tero-fs-meta);
/* ═══ GALERIE, FILTRES, PAGINATION ═════════════════════════════════════ */
--gallery-gap: var(--tero-space-8);
--gallery-radius: var(--tero-radius-media);
--gallery-caption-bg: var(--ui-image-overlay);
--filter-chip-h: 2.25rem;
--filter-chip-radius: var(--tero-radius-pill);
--filter-chip-border: 1px solid var(--ui-border-strong);
--filter-chip-bg-active:var(--ui-action-bg);
--filter-chip-fg-active:var(--ui-action-fg);
--pagination-gap: var(--tero-space-8);
--pagination-size: 2.75rem;
--pagination-fg: var(--ui-fg-muted);
--pagination-fg-current:var(--ui-fg);
--pagination-border-current: 2px solid var(--ui-accent);
}
}
Écart assumé au « trois rayons » [REGLE] : la charte autorise trois rayons d'angle
(bouton, plaque, média). Un quatrième est ajouté — 0.5rem **réservé aux champs de
saisie et aux notifications flottantes** (jamais un conteneur de contenu). Justification : un
champ en pilule (999 px) est inhabituel et, surtout, un champ à rayon 0 au milieu d'un
système arrondi signale une négligence. Si le client refuse le 4ᵉ rayon, la règle devient
« les champs prennent --radius-media (12 px) » — à trancher, mais **la décision doit être
écrite**, pas laissée au composant.
3.7 Matrice de contrastes RECALCULÉE (WCAG 2.1, méthode de luminance relative)
Tous les couples nouveaux ou corrigés, calculés le 15/10/2026 dans ce document :
21 valeurs neuves que les fichiers du lead ne contenaient pas.
Textes sur fonds clairs
| Couleur | sur #FFFFFF | sur #F6F4F4 | Verdict | Usage autorisé |
|---|---|---|---|---|
#000000 | 21,00:1 | 19,17:1 | AAA | tout |
#212121 | 16,10:1 | 14,70:1 | AAA | tout |
#004B69 pétrole | 9,50:1 | 8,67:1 | AAA | texte, liens |
#B93815 terracotta | 5,77:1 | 5,27:1 | AA | erreur, chiffres |
#007864 teal | 5,43:1 | 4,95:1 | AA | texte, liens |
#6B6B6B | 5,33:1 | 4,86:1 | AA | mentions |
#767676 | 4,54:1 | 4,15:1 | AA | bordure de champ |
#323232 | 12,82:1 | 11,70:1 | AAA | texte secondaire, placeholders |
#B9B9B9 | 1,96:1 | 1,79:1 | échec | jamais du texte (C8) |
#D9D9D9 | 1,41:1 | 1,29:1 | non-texte | filets décoratifs seuls (C11) |
#97AB83 sauge dérive | 2,48:1 | 2,36:1 | échec | interdit |
Aplats : texte à poser dessus
| Aplat | texte blanc | texte noir | Décision |
|---|---|---|---|
#007864 teal | 5,43:1 AA | 3,87:1 | blanc |
#004B69 pétrole | 9,50:1 AAA | 2,21:1 | blanc |
#B93815 terracotta | 5,77:1 AA | 3,64:1 | blanc |
#FAD282 sable | 1,44:1 | 14,61:1 AAA | noir |
#D7B4B4 rose poudré | 1,90:1 | 11,08:1 AAA | noir |
#00B7FF cyan | 2,28:1 | 9,22:1 AAA | noir |
#FF1D58 rose vif | 3,77:1 échec | 5,57:1 AA | noir (le blanc échoue) |
#000000 noir | 21,00:1 | — | blanc |
Fond profond : ce qui survit
| Couleur | sur #000000 | sur #212121 | Usage sur bande profonde |
|---|---|---|---|
#FFFFFF blanc | 21,00:1 | 16,10:1 | texte (toujours) |
#FAD282 sable | 14,61:1 | 11,20:1 | accent interactif, focus |
#D7B4B4 rose poudré | 11,08:1 | 8,49:1 | accent, sur-titre |
#00B7FF cyan | 9,22:1 | 7,07:1 | accent, icône |
#FF1D58 rose vif | 5,57:1 | 4,27:1 | texte sur #000 ; grand texte seulement sur #212121 |
#007864 teal | 3,87:1 (échec AA) | 2,97:1 (échec 3:1) | jamais — ni texte, ni focus (C9) |
#B93815 terracotta | 3,64:1 échec | 2,79:1 échec | jamais |
#000000 / #212121 | — | — | filet : rgb(255 255 255 / .18) |
Verdict opérationnel : sur bande profonde, le seul accent sûr est le sable
(11,20:1 sur #212121, 14,61:1 sur #000000, texte noir dessus à 14,61:1).
C'est la correction de C16, chiffrée.
3.8 Le JSON machine — design-tokens.json v2
{
"$schema": "https://open-design.ai/schemas/design-tokens.v2.json",
"name": "TERO GROUP",
"version": "2.0.0",
"source": "audit mesuré de www.tero.be, 15/10/2026 (docs/AUDIT-TERO-BE-2026.md) + relevé lead du 15/10/2026 pour les accents de sous-marque",
"doctrine": {
"regle0": "La marque existe déjà : on ne l'invente pas, on ne l'interprète pas.",
"regle1": "On mesure, on ne suppose pas.",
"regle2": "Le groupe ne se dissout pas dans ses sous-marques.",
"regle3": "Une sous-marque surcharge un accent et une imagerie — jamais la typographie, la grille ni les composants."
},
"layers": ["primitive", "semantic", "theme", "component"],
"cascade": {
"mechanism": "@layer",
"order": ["tero.primitives", "tero.compat", "tero.semantique", "tero.theme", "tero.composants", "tero.pages"]
},
"primitive": {
"color": {
"neutral": { "black": "#000000", "white": "#FFFFFF", "surface": "#F6F4F4",
"line": "#D9D9D9", "lineSoft": "#E5E6E5", "lineStrong": "#767676",
"inkSoft": "#323232", "inkDeep": "#212121", "disabled": "#B9B9B9" },
"brand": { "teal": "#007864", "petrol": "#004B69", "terracotta": "#B93815",
"sand": "#FAD282", "rose": "#D7B4B4", "cyan": "#00B7FF", "pink": "#FF1D58" },
"drift": { "forbidden": true,
"values": ["#97AB83", "#B00000", "#680224", "#550D24", "#B8451F", "#8BB169",
"#183F3C", "#5E93A7", "#D9EAD3", "#FFF5E5", "#F0E6D7", "#DACEC7",
"#D5E9F1", "#F9F4EC", "#FF4A4A",
"#075F83", "#068973", "#F4C567", "#CEA4A4", "#292929"] },
"occurrences": { "white": 1443, "black": 1269, "sage": 620, "surface": 388, "inkSoft": 243,
"inkDeep": 188, "disabled": 142, "line": 130, "teal": 47, "petrol": 21,
"sand": 16, "cyan": 15, "terracotta": 15, "rose": 12, "pink": 1 }
},
"typography": {
"families": { "display": "Almarose", "fallback": "Almarose Fallback", "script": "WildYouth" },
"weightsAvailable": [400, 500, 600, 700, 900],
"weightsForbidden": [100, 200, 300, 800],
"fallbackMetrics": { "sizeAdjust": "85.08%", "ascentOverride": "90.5%",
"descentOverride": "21.2%", "lineGapOverride": "3.3%" },
"ramp": {
"label": { "value": "clamp(0.75rem, 0.7292rem + 0.0926vw, 0.8125rem)", "bounds": "12-13px", "lh": 1.20 },
"meta": { "value": "clamp(0.8125rem, 0.7917rem + 0.0926vw, 0.875rem)", "bounds": "13-14px", "lh": 1.45 },
"bodyS": { "value": "clamp(0.875rem, 0.8542rem + 0.0926vw, 0.9375rem)", "bounds": "14-15px", "lh": 1.65 },
"body": { "value": "clamp(0.9375rem, 0.9167rem + 0.0926vw, 1rem)", "bounds": "15-16px", "lh": 1.65 },
"lead": { "value": "clamp(1.0625rem, 1.0417rem + 0.0926vw, 1.125rem)", "bounds": "17-18px", "lh": 1.60 },
"titleS": { "value": "clamp(1.25rem, 1.1458rem + 0.463vw, 1.5625rem)", "bounds": "20-25px", "lh": 1.25 },
"titleM": { "value": "clamp(1.875rem, 1.8333rem + 0.1852vw, 2rem)", "bounds": "30-32px", "lh": 1.15 },
"titleL": { "value": "clamp(2.5rem, 2.2917rem + 0.9259vw, 3.125rem)", "bounds": "40-50px", "lh": 1.08 },
"display": { "value": "clamp(3.75rem, 3.3333rem + 1.8519vw, 5rem)", "bounds": "60-80px", "lh": 1.02 }
},
"measure": { "chapo": "58ch", "body": "66ch", "faq": "62ch", "side": "42ch" }
},
"space": { "scale": "8pt", "steps": [4,8,12,16,24,32,48,64,96,128,192],
"rhythm": { "flush": "clamp(2rem, 1.75rem + 1.1111vw, 2.75rem)",
"tight": "clamp(2.75rem, 2.3333rem + 1.8519vw, 4rem)",
"normal": "clamp(3.75rem, 3rem + 3.3333vw, 6rem)",
"loose": "clamp(4.5rem, 3.5rem + 4.4444vw, 7.5rem)" },
"betweenEntries": "2.5rem" },
"layout": { "container": "1100px", "containerText": "760px",
"gutter": "clamp(1rem, 0.8333rem + 0.7407vw, 1.5rem)", "columns": 12,
"breakpoints": { "xs": "360px", "sm": "390px", "md": "768px", "lg": "1024px", "xl": "1440px" } },
"radius": { "media": "12px", "plate": "20px", "pill": "999px", "field": "8px",
"measuredDistinct": 15,
"note": "12px est un choix, pas la valeur mesurée dominante (20px, 188 occurrences)." },
"motion": {
"easing": { "outStrong": "cubic-bezier(0.23, 1, 0.32, 1)",
"inOutSoft": "cubic-bezier(0.77, 0, 0.175, 1)",
"drawer": "cubic-bezier(0.32, 0.72, 0, 1)" },
"duration": { "fast": "140ms", "base": "200ms", "move": "240ms",
"draw": "380ms", "slow": "680ms", "marquee": "55s" }
},
"elevation": { "float": "0 1px 2px rgb(0 0 0 / .04), 0 12px 28px -14px rgb(0 0 0 / .14)",
"cards": "none", "reason": "la charte interdit la carte empilée et les ombres lourdes" },
"zIndex": { "base": 0, "sticky": 10, "header": 20, "dropdown": 30,
"overlay": 40, "drawer": 50, "modal": 60, "toast": 70 }
},
"semantic": {
"fields": ["light-a", "light-b", "deep", "accent"],
"tokens": {
"surface": "var(--tero-white)", "surfaceAlt": "var(--tero-surface)",
"fg": "var(--tero-black)", "fgMuted": "var(--tero-ink-soft)", "fgSubtle": "#6B6B6B",
"disabledFg": "#767676", "border": "var(--tero-line)", "borderStrong": "var(--tero-line-strong)",
"accent": "var(--tero-teal)", "accentText": "var(--tero-teal)", "accentOn": "var(--tero-white)",
"actionBg": "var(--tero-black)", "actionFg": "var(--tero-white)",
"focus": "var(--tero-teal)", "focusHalo": "var(--tero-white)",
"danger": "var(--tero-terracotta)", "success": "var(--tero-teal)", "info": "var(--tero-petrol)"
},
"themeOverridable": ["ui-accent", "ui-accent-on", "ui-accent-text", "ui-accent-hover",
"ui-accent-soft", "ui-deep", "ui-deep-fg", "ui-focus", "ui-focus-halo",
"ui-image-treatment", "ui-decoration"],
"themeLocked": ["toute la couche primitive", "typographie", "espacements", "grille",
"conteneur", "rayons", "durées et courbes", "cibles tactiles",
"focus (géométrie)", "empilement", "ombres", "tous les tokens de composant"]
},
"contract": {
"bg": "var(--ui-surface)", "surface": "var(--ui-surface-alt)", "fg": "var(--ui-fg)",
"muted": "var(--ui-fg-muted)", "border": "var(--ui-border)", "accent": "var(--ui-accent)"
},
"contrast": {
"method": "WCAG 2.1, luminance relative — recalculé le 15/10/2026 dans docs/design/EXPERT-02",
"textOnLight": { "black": "21.00:1", "inkDeep": "16.10:1", "petrol": "9.50:1",
"terracotta": "5.77:1", "teal": "5.43:1", "grey6B": "5.33:1",
"grey76": "4.54:1", "inkSoft": "12.82:1" },
"onSurface": { "inkSoft-on-surface": "11.70:1", "terracotta-on-surface": "5.27:1",
"teal-on-surface": "4.95:1", "line-on-surface": "1.29:1" },
"onAccent": { "white-on-teal": "5.43:1", "white-on-petrol": "9.50:1",
"white-on-terracotta": "5.77:1", "black-on-sand": "14.61:1",
"black-on-rose": "11.08:1", "black-on-cyan": "9.22:1",
"black-on-pink": "5.57:1", "white-on-pink": "3.77:1" },
"onDeep": { "sand-on-black": "14.61:1", "sand-on-inkDeep": "11.20:1",
"rose-on-inkDeep": "8.49:1", "cyan-on-inkDeep": "7.07:1",
"pink-on-black": "5.57:1", "pink-on-inkDeep": "4.27:1",
"teal-on-inkDeep": "2.97:1", "teal-on-black": "3.87:1",
"terracotta-on-inkDeep": "2.79:1" },
"forbidden": ["teal-on-black-as-text", "teal-as-focus-on-deep", "muted-B9B9B9-as-text",
"line-D9D9D9-as-field-border", "white-on-pink", "sage-on-white"],
"exempt": ["contrôles désactivés (WCAG 1.4.3/1.4.11)", "filets purement décoratifs"]
},
"themeRegistry": {
"tero-group": { "accent": "#007864", "accentOn": "#FFFFFF", "accentText": "#007864",
"deep": "#000000", "deepFg": "#FFFFFF", "status": "mesuré, conforme" },
"teropadelclub": { "accent": "#FF2E82", "accentOn": "#000000", "accentText": "#D9216B",
"deep": "#0D1F30", "deepFg": "#FFFFFF",
"status": "text-safe fourni par la charte padel (#D9216B 4,79:1) ; magenta interdit en texte (3,52:1)" },
"terostudio": { "accent": "#7C8F7A", "accentOn": "#000000",
"accentText": null, "status": "accentText MANQUANT : 3,46:1 sur blanc — à fournir par le studio" },
"terotrail": { "accent": "#2B4336", "accentOn": "#FFFFFF", "accentText": "#2B4336",
"accentSoft": "#8FB8A5", "status": "conforme (10,72:1)" },
"immorun": { "accent": "#263062", "accentOn": "#FFFFFF", "accentText": "#263062",
"accentSoft": "#E0BB8E", "status": "conforme (12,49:1)" },
"batirun": { "accent": "#C0001E", "accentOn": "#FFFFFF", "accentText": "#C0001E",
"status": "conforme (6,44:1)" },
"brusselspremierpadel": { "accent": "#A56933", "accentOn": "#000000",
"accentText": "#A56933",
"status": "LIMITE : 4,50:1 exactement — aucune marge, à durcir de 5 %" },
"garisartsportsclub": { "accent": "#AAC440", "accentOn": "#000000",
"accentText": null, "status": "accentText MANQUANT : 1,97:1 sur blanc" }
},
"brand": {
"wordmark": "tero",
"wordmarkCase": "minuscules",
"proseCase": "Tero",
"entityCase": "Tero Group",
"signature": "live. share. reconnect.",
"monogram": { "file": "_audit/monogram-tero-2026.png", "nature": "glyphe o (anneau + barre) du mot-symbole",
"aspectRatio": 0.6835, "square": false, "usage": "jamais Organization.logo ; favicon à arbitrer" }
},
"files": { "primitives": "tokens/01-primitives.css", "semantic": "tokens/02-semantique.css",
"components": "tokens/03-composants.css", "themes": "themes/", "compat": "tokens/99-compat.css" }
}
---
4. DÉCLINAISON PAR SOUS-MARQUE — le mécanisme
4.1 Le principe en une phrase
**Une sous-marque surcharge un accent et une imagerie. Elle ne surcharge ni la typographie,
ni la grille, ni les composants, ni les états.** [REGLE]
C'est la seule façon de tenir les deux promesses contradictoires du projet : des marques
reconnaissables (magenta padel, sauge studio, navy immorun) et une expérience unique
(une rampe, une grille, des composants, une accessibilité, un budget). Le modèle de référence
a résolu le problème par accident (il est mono-marque) ; le groupe doit le résoudre **par
construction**.
4.2 VERROUILLÉ ou SURCHARGEABLE — le tableau sans ambiguïté
| Élément | Statut | Valeur / règle | Ce qu'une sous-marque a le droit d'écrire |
|---|---|---|---|
| Accent (aplat de marque) | SURCHARGEABLE | --ui-accent | une couleur mesurée sur son site |
| Texte sur accent | SURCHARGEABLE, obligatoire | --ui-accent-on = noir ou blanc | obligatoire dès qu'un accent est déclaré |
| Accent en texte sur fond clair | SURCHARGEABLE, contrôlé | --ui-accent-text ≥ 4,5:1 | sinon null → les liens restent en encre |
| Survol d'accent | SURCHARGEABLE | --ui-accent-hover ≥ 3:1 avec l'accent | une variante plus dense |
| Aplat doux d'encart | SURCHARGEABLE | --ui-accent-soft | une teinte très claire de l'accent |
| Bande profonde | SURCHARGEABLE | --ui-deep, --ui-deep-fg | une couleur de fond + l'encre qui tient 4,5:1 |
| Anneau de focus | SURCHARGEABLE (couleur) / VERROUILLÉ (géométrie) | --ui-focus ≥ 3:1 sur tous les champs de la marque ; 3 px / offset 3 px | une couleur, jamais une épaisseur |
| Traitement d'image | SURCHARGEABLE | --ui-image-treatment | none ou un filtre (duotone, désaturation) |
| Décoration / motif | SURCHARGEABLE | --ui-decoration | un motif de points, une trame — jamais un logo redessiné |
| Famille typographique | VERROUILLÉ | Almarose + WildYouth | rien. Aucune 3ᵉ famille, aucune serif |
| Graisses | VERROUILLÉ | 400/500/600/700/900 (fichiers existants) | rien |
| Rampe typographique | VERROUILLÉ | 9 paliers fluides, § 3.4 | rien |
| Interlignage / interlettrage | VERROUILLÉ | § 3.4 | rien |
| Mesures de lecture | VERROUILLÉ | 58/62/66/42 ch | rien |
| Échelle d'espacement | VERROUILLÉ | 8 pt complète, § 3.1 | rien |
| Rythme de section | VERROUILLÉ | 4 profils --tero-rhythm-* | choisir un profil, pas une valeur |
| Grille et conteneur | VERROUILLÉ | 12 colonnes, 1 100 px (cf. C23) | rien |
| Points de rupture | VERROUILLÉ | 360/390/768/1024/1440 | rien |
| Rayons | VERROUILLÉ | 12/20/999 + 8 (champs) | rien |
| Filets | VERROUILLÉ | 1 px | rien |
| Durées et courbes | VERROUILLÉ | 3 courbes, 6 durées, § 3.1 | rien |
| États | VERROUILLÉ | les 7 états (§ 5.0) | rien : un composant sans état complet est refusé |
| Focus (géométrie, visibilité) | VERROUILLÉ | 3 px + offset 3 px + halo, jamais supprimé | rien |
| Cibles tactiles | VERROUILLÉ | 44 px | rien |
Empilement (z-index) | VERROUILLÉ | échelle nommée | rien |
| Ombres | VERROUILLÉ | une seule ombre flottante | rien |
| Tokens de composant | VERROUILLÉ | § 3.6 | rien |
| Structure des gabarits | VERROUILLÉ | § 6 | le contenu, pas l'ordre |
| Endossement Tero | VERROUILLÉ | mention + lien tero.be | rien : jamais retiré |
| Logo | VERROUILLÉ (fichiers) | fichiers officiels tels quels | choisir la variante autorisée, pas la redessiner |
Formulation opposable, à recopier dans tero-group-rules.md :
Une sous-marque peut changer ce qui est coloré, jamais ce qui est mesuré ou structurant.
Toute variable absente du tableau ci-dessus est verrouillée par défaut.
4.3 Le fichier de thème — modèle et exemples réels
Un thème = un fichier, une quinzaine de lignes, zéro sélecteur de composant.
/* themes/teropadelclub.css — accents MESURÉS (relevé lead 15/10/2026) + charte padel */
@layer tero.theme {
[data-theme='teropadelclub'] {
--ui-accent: #ff2e82; /* magenta signature — 3,52:1 sur blanc : DÉCORATIF */
--ui-accent-on: #000000; /* noir sur magenta = 5,97:1 (le blanc échoue à 3,52:1) */
--ui-accent-text: #d9216b; /* variante texte fournie par la charte padel — 4,79:1 */
--ui-accent-hover: #e6296f; /* [fourni charte padel] */
--ui-accent-soft: #fff0f6; /* [REGLE] encart clair, à valider en QA */
--ui-deep: #0d1f30; /* navy du logo padel */
--ui-deep-fg: #ffffff; /* 16,5:1 environ */
--ui-focus: #ff2e82; /* ≥ 3:1 sur les champs padel (blanc, crème, navy) */
--ui-decoration: radial-gradient(circle, currentColor 4.5px, transparent 4.6px) 0 0 / 18px 18px;
/* RIEN D'AUTRE. Aucune taille, aucun rayon, aucune durée, aucun token de composant. */
}
}
/* themes/terotrail.css — forêt : l'accent TIENT le texte (10,72:1) */
@layer tero.theme {
[data-theme='terotrail'] {
--ui-accent: #2b4336;
--ui-accent-on: #ffffff; /* blanc sur forêt : 10,72:1 */
--ui-accent-text: #2b4336; /* texte sur blanc : 10,72:1 — conforme, rien à dériver */
--ui-deep: #1c2c23; /* [A VALIDER CLIENT] : un fond profond propre à Tero Trail */
--ui-deep-fg: #ffffff;
--ui-focus: #2b4336;
}
}
/* themes/terostudio.css — sauge : l'accent NE TIENT PAS le texte (3,46:1) */
@layer tero.theme {
[data-theme='terostudio'] {
--ui-accent: #7c8f7a; /* relevé lead */
--ui-accent-on: #000000; /* noir sur sauge : 6,07:1 — conforme */
--ui-accent-text: null; /* 3,46:1 sur blanc : ÉCHEC → aucun texte accentué */
--ui-deep: #22271f; /* [A VALIDER CLIENT] */
--ui-deep-fg: #ffffff;
--ui-focus: #7c8f7a; /* 3,46:1 : au-dessus de 3:1, mais sans marge */
}
}
Le cas --ui-accent-text: null est la partie importante du mécanisme. Sur les huit marques
enregistrées, deux n'ont aucun accent lisible en texte sur blanc — Tero Studio (3,46:1) et
Garisart (1,97:1) — et une troisième, Bruxelles Premier Padel, est à la limite exacte (4,50:1) (§ 4.5). Plutôt que d'inventer une teinte
dérivée — ce qui est exactement la dérive à 164 valeurs — le design system prend une décision
de repli unique et vérifiable :
**[REGLE] Quand
--ui-accent-textn'est pas fourni et mesuré ≥ 4,5:1, l'accent n'estjamais du texte.** Les liens du corps de texte passent en
--ui-fg(noir) et restentsoulignés (WCAG 1.4.1 les y oblige de toute façon) ; l'accent ne vit que dans les aplats,
filets, pastilles et pictogrammes, sur un fond dont le contraste est vérifié.
Conséquence : la marque reste identifiable (aplats, encarts, bandes, survols, images) sans
qu'aucun texte ne tombe sous AA. C'est la seule issue qui respecte à la fois la charte
« on n'invente rien » et la charte « AA partout ».
4.4 Attacher un thème : un attribut, un seul
<html lang="fr-BE" data-theme="teropadelclub"> <!-- thème de marque -->
<body>
<section data-field="light-a"> … <!-- fond blanc -->
<section data-field="light-b"> … <!-- fond #F6F4F4 -->
<section data-field="deep"> … <!-- fond de la marque, encre adaptée -->
<section data-field="accent"> … <!-- aplat de marque, encre adaptée -->
</body>
</html>
| Règle | Détail | Vérification automatique |
|---|---|---|
data-theme sur <html> | une seule valeur, jamais sur un sous-arbre | 1 occurrence par page |
data-field sur chaque section | obligatoire si le fond n'est pas light-a | toute section avec un fond non blanc sans data-field = erreur de build |
| thème absent | data-theme="tero-group" est la valeur par défaut (posée par le gabarit, pas par l'auteur) | — |
| thème d'une marque sur une page groupe | interdit | la page groupe est toujours en tero-group |
Pourquoi un attribut et non une classe : le thème doit pouvoir être changé par le CMS, par un
aperçu d'admin ou par un test, sans toucher au CSS ni au composant. C'est aussi ce qui rend
la déclinaison testable : ?theme=terotrail suffit à passer toute la suite de tests.
4.5 La table des accents de sous-marque — mesurés et jugés
Relevé lead du 15/10/2026. Les ratios sont recalculés dans ce document (WCAG 2.1).
| Marque | Accent | sur blanc (texte) | blanc sur aplat | noir sur aplat | sur #212121 | Verdict --ui-accent-text | --ui-accent-on |
|---|---|---|---|---|---|---|---|
| Tero Group | #007864 | 5,43:1 | 5,43:1 | 3,87:1 | 2,97:1 échec | accent lui-même ✅ | #FFFFFF |
| Tero Padel Club | #FF2E82 | 3,52:1 | 3,52:1 échec | 5,97:1 | 4,57:1 | #D9216B fourni ✅ | #000000 |
| Tero Padel Club | #E9F44E (lime) | 1,20:1 | 1,20:1 | 17,52:1 | 13,43:1 | non (aplat) | #000000 |
| Tero Studio | #7C8F7A (sauge) | 3,46:1 | 3,46:1 | 6,07:1 | 4,65:1 | manquant ❌ | #000000 |
| Tero Trail | #2B4336 (forêt) | 10,72:1 | 10,72:1 | 1,96:1 | 1,50:1 | accent lui-même ✅ | #FFFFFF |
| Tero Trail | #8FB8A5 (forêt clair) | 2,19:1 | 2,19:1 | 9,57:1 | 7,34:1 | non (aplat / accent sur fond profond) | #000000 |
| Immorun | #263062 (navy) | 12,49:1 | 12,49:1 | 1,68:1 | 1,29:1 | accent lui-même ✅ | #FFFFFF |
| Immorun | #E0BB8E (sable) | 1,80:1 | 1,80:1 | 11,67:1 | 8,95:1 | non (aplat) | #000000 |
| Batirun | #C0001E (rouge) | 6,44:1 | 6,44:1 | 3,26:1 | 2,50:1 | accent lui-même ✅ | #FFFFFF |
| Batirun | #0E143C (encre) | 17,74:1 | 17,74:1 | 1,18:1 | 1,10:1 | accent lui-même ✅ | #FFFFFF |
| Bruxelles Premier Padel | #A56933 (orange) | 4,50:1 ⚠️ | 4,50:1 | 4,67:1 | 3,58:1 | limite exacte ⚠️ | #000000 |
| Garisart Sports Club | #AAC440 (lime) | 1,97:1 | 1,97:1 | 10,68:1 | 8,19:1 | manquant ❌ | #000000 |
| tero.be — sauge dérive | #97AB83 | 2,48:1 | 2,48:1 | 8,47:1 | 6,49:1 | interdit | — |
Quatre enseignements mesurés :
- Aucun accent ne fonctionne à la fois comme texte sur blanc et comme aplat à texte blanc.
C'est la preuve chiffrée qu'un thème à une variable est intenable : il en faut trois
(--ui-accent, --ui-accent-on, --ui-accent-text). C'est exactement ce que la charte
padel a fini par faire à la main (clay / clay-ink / clay-text) — on le généralise.
- Six des douze accents relevés échouent nettement en texte sur blanc : magenta padel (3,52),
sauge studio (3,46), sauge dérive tero.be (2,48), forêt clair trail (2,19), sable immorun (1,80),
lime garisart (1,97) ; un septième, l'orange BPP (4,50), est à la limite exacte. Sans le repli
§ 4.3, Tero Studio et Garisart livreraient des liens sous AA, et BPP n'aurait aucune marge.
- **Le « vert sauge » de tero.be (
#97AB83, 620 occurrences) n'est pas la sauge de Tero Studio
(#7C8F7A).** Écart entre les deux : ce ne sont pas la même couleur. La question ouverte n° 1
du client (« la sauge est-elle une couleur de marque ou une dérive ? ») a donc une réponse
documentable : **la sauge est bien une couleur de marque du groupe — celle de Tero Studio —
mais la valeur qui court sur tero.be n'est pas la bonne.** [MESURE] + [A VALIDER CLIENT].
- BPP
#A56933= 4,50:1 exactement. Une seule unité de teinte en dessous fait échouer.
Aucun système ne doit reposer sur une marge nulle : à durcir d'au moins 5 % de luminance
relative, ou à traiter comme les six autres (§ 4.3).
4.6 Les marques événementielles autonomes — Immorun, Batirun, BPP26
Ces marques ont une identité propre, un logo dédié et un cycle de vie court (une édition).
Le mécanisme les traite sans créer un 4ᵉ type de thème :
| Cas | Mécanisme | Exemple |
|---|---|---|
| Marque permanente du groupe | data-theme="<marque>" + pilier déclaré | teropadelclub, terostudio, terotrail |
| Marque événementielle (une édition) | même data-theme, + data-theme-scope="event" | immorun, batirun, brusselspremierpadel |
| Co-marque (sponsor, partenaire) | emplacement dédié --brandbar-* en monochrome, jamais un thème | co-marque IMMOBEL sur Immorun |
/* Une marque événementielle ajoute DEUX règles, pas un système : */
[data-theme-scope='event'] {
--hero-min-h: 38rem; /* un événement a le droit à un hero plus haut */
--brandbar-mono: 0; /* les logos partenaires peuvent garder leur couleur */
}
Règles opposables pour les événements [REGLE] :
- Endossement Tero obligatoire : nom de l'événement + mention « un événement du groupe
Tero » + lien vers tero.be, dans l'en-tête ou le pied de page — jamais les deux à la fois.
Le logo de l'événement (IMMORUN, BATIRUN, BPP26) est utilisé tel quel, à côté — jamais
dans — le mot-symbole Tero.
- Aucun lockup inventé. « tero immorun », « IMMORUN by Tero », une fusion de tracés :
interdits. Si le client veut un endossement graphique, il est dessiné par le studio.
- Co-marque : emplacement séparé, hauteur optique unifiée (
--brandbar-logo-h), jamais
dans le même bloc que le logo Tero, et le sponsor n'apparaît jamais comme éditeur du site.
- Fin d'édition : le thème événementiel est retiré, pas archivé dans la CSS. Un thème
qui survit à son événement est de la dette (c'est le mécanisme qui a produit les 536 feuilles).
- Une marque événementielle ne touche pas aux documents structurés de l'entité Tero :
Organization reste Tero Group ; l'événement est un Event avec organizer = Tero.
4.7 Porte de validation d'un thème (bloquante)
Un thème n'est fusionnable que si les 9 contrôles passent, exécutés par le script de
vérification (§ 8) :
| # | Contrôle | Seuil | Sinon |
|---|---|---|---|
| T1 | Le fichier ne contient que des variables de la liste blanche § 3.5 | 0 variable hors liste | build en échec |
| T2 | Aucune occurrence de --tero- dans un fichier de thème | 0 | build en échec |
| T3 | Aucun sélecteur autre que [data-theme='…'] et [data-theme-scope='event'] | 0 | build en échec |
| T4 | --ui-accent-on déclaré si --ui-accent l'est | obligatoire | build en échec |
| T5 | contrast(--ui-accent, --ui-accent-on) ≥ 4,5 | calculé | build en échec |
| T6 | contrast(--ui-accent-text, #FFFFFF) ≥ 4,5 ou --ui-accent-text: null | calculé | build en échec |
| T7 | contrast(--ui-focus, --ui-surface) ≥ 3 sur les 4 champs du thème | calculé | build en échec |
| T8 | Nombre de couleurs déclarées dans le thème ≤ 5 | ≤ 5 | avertissement bloquant |
| T9 | Le thème est enregistré dans content/sources/tero-brands.json avec son pilier | présent | build en échec |
T8 mérite une justification : les 15+ valeurs de dérive mesurées sur tero.be viennent
d'ajouts successifs jamais comptés. Plafonner à 5 valeurs par thème (accent, accent-on,
accent-text, deep, deep-fg) rend l'inflation structurellement impossible : au 6ᵉ token,
on doit supprimer, pas ajouter.
---
5. INVENTAIRE DES COMPOSANTS
5.0 Les règles communes — à lire avant tout composant
Les 7 états sont obligatoires pour tout composant interactif (règle groupe § 7.4). Un état
non déclaré est un défaut bloquant.
| État | Déclencheur | Traduction visuelle autorisée | Token d'état | Exigence d'accessibilité |
|---|---|---|---|---|
| repos | — | état de référence | --bg, --fg | contraste ≥ 4,5:1 (texte) / 3:1 (composant) |
survol (hover) | souris uniquement | changement de fond, de couleur, soulignement | --*bg-hover | toujours gardé par @media (hover:hover) and (pointer:fine) ; jamais porteur d'une information exclusive |
| focus visible | clavier | anneau 3 px + offset 3 px + halo | --ui-focus | ≥ 3:1 sur tous les champs ; jamais outline:none ; ≥ 1 px d'épaisseur (2.4.13) |
| actif | appui maintenu | transform: scale(0.97), jamais un déplacement | --btn-1-active-scale | aucune information portée par l'échelle |
| désactivé | non applicable | encre --ui-disabled-fg (4,54:1), fond --ui-surface-alt | --disabled- | exempt de contraste (1.4.3/1.4.11) mais jamais invisible ; aria-disabled préféré à disabled si le champ doit rester annoncé |
| vide | aucune donnée | phrase + porte de sortie (CTA) — jamais « Aucun résultat. » | --ui-fg-muted | le message est dans le flux, lu par le lecteur d'écran (role="status") |
| erreur | saisie invalide | bordure --ui-danger 2 px + message lié | --field-*-invalid | aria-invalid, aria-describedby, cause + correction |
| chargement | attente | squelette aux dimensions finales, ou libellé au participe dans le bouton | --ui-fg-muted | aria-busy, annonce role="status" ; au-delà de 1 s un indicateur, au-delà de 10 s une phrase |
Six règles transverses à tous les composants :
- Aucun composant n'écrit une couleur en dur. Il lit
--ui-(champ) ou--<composant>-.
Un #hex dans un fichier de composant = échec du contrôle § 8.2.
- Aucune boîte empilée : pas de
background+border+radius+box-shadow
simultanés sur une carte de contenu. La séparation vient du filet ou de l'espace.
- Le titre porte la carte, pas la boîte : hiérarchie
--card-title-*, pas de fond. - Un seul arrêt de tabulation par carte : le motif est
<article><a class="card-link" href>
couvrant la carte par un ::after positionné, jamais de liens imbriqués. Le libellé
accessible du lien contient le titre de la carte (aria-label ou contenu masqué).
- Toute image a
width/height(ouaspect-ratio) :--card-media-ratioetc.
Un alt vide uniquement pour une image décorative ; un visuel porteur de sens a un alt réel.
- Aucun emoji comme icône : SVG inline,
currentColor, trait 1,5-2 px,aria-hiddensauf
s'il est le seul contenu du contrôle (alors aria-label sur le contrôle).
---
C-01 · Bouton — 3 niveaux · btn
| Anatomie | <button class="btn btn--primary"> → icône optionnelle (avant/après) · libellé · indicateur de chargement interne | ||
| Variantes | btn--primary (aplat) · btn--secondary (contour 1 px) · btn--tertiary (lien-bouton souligné, sans fond) · btn--danger (actions destructives, --ui-danger) | ||
| Tailles | md (défaut, min-h 44 px, pad 12/24, 14 px) · lg (min-h 52 px, pad 16/32, 15 px) · sm (uniquement dans un tableau ou une barre d'outils, min-h 44 px conservé) | ||
| Tokens | `--btn-1-bg | fg | bg-hover, --btn-2-, --btn-3-, --btn-radius (999), --btn-min-h, --btn-transition (--tero-dur-fast`) |
| États spécifiques | chargement : libellé au participe (« Envoi… » → « Envoyé »), spinner dans le bouton, aria-busy="true", largeur stable (le texte change sans reflow) | ||
| Usage | un seul btn--primary par écran ; le libellé est un verbe d'action (« Réserver un terrain », pas « En savoir plus ») ; jamais un bouton pour naviguer vers une URL connue (c'est un lien), jamais un lien pour déclencher une action | ||
| A11y | cible ≥ 44 × 44 px · contraste ≥ 4,5:1 · focus-visible continu (2.4.13) · un bouton icône seule porte aria-label · jamais title comme seul libellé · les icônes décoratives sont aria-hidden="true" | ||
| Anti-pattern | deux boutons pleins côte à côte · un bouton « En savoir plus » à côté d'un lien identique · un <div role="button"> sans gestion clavier |
C-02 · Lien · link
| Anatomie | <a href> · texte · éventuel indicateur de sortie (↗ + aria-label « ouvre un nouvel onglet ») |
| Variantes | link--inline (dans le corps) · link--standalone (en dehors du corps, peut perdre le soulignement) · link--back · link--external |
| Tailles | hérite de --tero-fs-body / --tero-fs-meta ; jamais sous --tero-fs-meta |
| Tokens | --link-fg, --link-underline-offset, --link-underline-thickness-hover |
| États | survol : soulignement épaissi (1 → 2 px), pas de changement de couleur seul ; focus : anneau ; visite : même couleur (pas de violet par défaut du navigateur) |
| Usage | le libellé décrit la destination (« Voir le club de Waterloo », pas « cliquez ici ») · un lien inline est toujours souligné (WCAG 1.4.1 : la couleur seule ne distingue pas) · dans une phrase, --link-inline-pad-block agrandit la cible verticalement |
| A11y | 1.4.1 (couleur) · 2.4.4 (objet du lien) · 2.4.9 (objet par le libellé seul) · 2.5.8 (cible ≥ 24 × 24 px, sauf lien en ligne) |
Cas --ui-accent-text: null | le lien prend --ui-fg (encre) et reste souligné : c'est le repli obligatoire des marques sans accent texte conforme (§ 4.3) |
C-03 · Carte d'entité — lieu / restaurant / activité / événement / article · card
| Anatomie (5 parties) | ① média (aspect-ratio, --card-media-ratio) → ② sur-titre (lieu, activité, événement) → ③ titre (H3, --card-title-*) → ④ une ligne de méta (ville · date · capacité · prix) → ⑤ action (lien-bouton tertiaire) |
| Variantes | card--place (lieu : ville + pays, pas de prix) · card--restaurant (cuisine + service) · card--activity (durée + niveau + âge) · card--event (date + lieu, badge d'édition) · card--article (date + temps de lecture) |
| Tailles | compact (dans un carrousel, média 3/2) · default (grille, média 3/2) · feature (média 16/9 pleine largeur, texte en dessous) |
| Tokens | --card-* (§ 3.6) · --card-bg: transparent, --card-shadow: none : c'est la règle « pas de boîte » rendue exécutable |
| États | survol : zoom du média --card-media-zoom: 1.03 sur --tero-dur-slow (transform seul, jamais la boîte) · focus : anneau sur toute la carte · média absent : background: --ui-surface-alt + le titre reste lisible (jamais un trou blanc) · chargement : squelette aux dimensions finales |
| Usage | la méta est une ligne : deux lignes de méta = deux informations sans hiérarchie · jamais de prix sur une carte de lieu sans source vérifiée · jamais une carte sans titre |
| A11y | un seul arrêt de tabulation (motif C-03 § 5.0.4) · alt réel sur le média (nom du lieu, pas « photo ») · titre en H3 sous un H2 de section (jamais de saut) · la date d'un événement est en <time datetime> |
C-04 · Grille de cartes · grid
| Anatomie | <ul class="grid"> de <li class="grid__item"> (liste sémantique : un lecteur d'écran annonce « 6 éléments ») |
| Variantes | grid--cards (auto-fit) · grid--places (2 colonnes asymétriques 7/5) · grid--logos (bandeau de marques) · grid--stats (bande de chiffres) |
| Tailles | 1 / 2 / 3 / 4 colonnes ; jamais plus de 4 entités par rangée ; seuil d'auto-fit --grid-min-card: 280px |
| Tokens | --grid-gap (24 px) · --grid-columns- · --card- |
| États | vide : bloc « Aucun lieu dans cette région » + lien vers l'index complet |
| Usage | alterner au moins deux ratios de colonnes par page (7/5 puis 8/4) : deux grilles identiques consécutives sont un défaut mesuré du site actuel · jamais deux fonds identiques adjacents |
| A11y | liste réelle, pas div + role="list" · ordre de lecture = ordre visuel (pas de order CSS) · aucune information portée par la position seule |
C-05 · Bandeau hero · hero
| Anatomie | ① média plein cadre (image eager+fetchpriority="high", ou vidéo dédiée portrait/paysage) → ② voile --hero-overlay → ③ sur-titre → ④ H1 → ⑤ chapô (≤ 58 ch) → ⑥ action(s) → ⑦ indicateur de défilement |
| Variantes | hero--full (100svh, jamais 100vh) · hero--split (texte / média côte à côte, ratio 7/5) · hero--compact (min-h --hero-min-h) · hero--event (compte à rebours, édition) |
| Tailles | --hero-min-h: 34rem → 100svh · contenu borné à --hero-content-max: 38rem |
| Tokens | --hero-* · --ui-image-overlay · --hero-scrim-min: 0.45 |
| États | chargement : poster affiché immédiatement (jamais de dégradé gris) · réduction de mouvement : vidéo jamais chargée, poster seul |
| Usage | un seul H1 par page, dans le hero · le texte du hero est lisible sans le média : si le voile disparaît, le contraste doit encore tenir 4,5:1 (test automatique : contraste au pixel le plus clair sous le texte) |
| A11y | contraste du texte sur photo vérifié sur le pixel le plus clair de la zone, pas sur la moyenne · une vidéo décorative est muted, loop, playsinline, aria-hidden, avec poster · aucun texte dans l'image |
| Anti-pattern mesuré | height: 100vh (dette relevée sur 49 pages du projet padel) |
C-06 · Bande profonde · band
| Anatomie | <section data-field="deep"> → sur-titre → H2 → chapô → contenu → action |
| Variantes | band--statement (une phrase, un accent) · band--stats (chiffres) · band--cta (action unique) · band--footer |
| Tailles | padding-block: --band-pad-block (profil normal) · loose au maximum une fois tous les quatre blocs |
| Tokens | --band-bg, --band-fg, --band-accent, --band-rule |
| États | — (bloc non interactif) |
| Usage | fond : #000000 ou #212121 (groupe) ou --ui-deep du thème · texte blanc · accent sable par défaut (11,20:1 sur #212121) · teal interdit (2,97:1) · deux bandes profondes ne se suivent jamais |
| A11y | le changement de champ est le contrat de contraste : data-field="deep" obligatoire, vérifié au build |
C-07 · Bande de chiffres · stats
| Anatomie | liste de paires (grand nombre + libellé court), séparées par un filet vertical |
| Variantes | stats--band (dans une bande profonde) · stats--inline (dans un paragraphe, style inline) · stats--kpi (avec un accent par chiffre) |
| Tailles | nombre --stat-size (40-50 px) desktop / --stat-size-mobile (30-32 px) ; libellé --stat-label-size (13-14 px) |
| Tokens | --stat-* · font-variant-numeric: tabular-nums |
| États | vide : jamais une bande de chiffres vide → le bloc disparaît (pas de « 0 ») |
| Usage | un chiffre ne s'affiche pas deux fois dans deux sections voisines (règle groupe) · chaque chiffre a une source (§ 12 de la charte) · posés directement sur la page, jamais des pastilles · la couleur des chiffres au niveau groupe = terracotta (règle § 2.3.3) |
| A11y | la séparation visuelle par filet n'est pas la seule séparation : le HTML est une liste de paires terme/valeur (<dl>) · les nombres sont lus sans aria-label inventé |
C-08 · Passage citable · quote
| Anatomie | ① sur-titre majuscules + filet → ② paragraphe autonome de 2-3 phrases (--quote-measure: 58ch) → ③ source (nom, fonction, société, <cite>) |
| Variantes | quote--brand (idée de marque, sans source externe) · quote--testimonial (verbatim client) · quote--pull (reprise courte, sans guillemet graphique) |
| Tailles | --quote-size (17-18 px) · --quote-mark-size (le guillemet est typographique, jamais une image) |
| Tokens | --quote-* |
| États | — |
| Usage | un seul par section · un chiffre dans le passage (c'est ce qui le rend citable) · les témoignages sont verbatim, avec nom et société, jamais reformulés · jamais « Ils nous font confiance » |
| A11y | <blockquote> + <cite> ; le guillemet décoratif est aria-hidden ; ne pas mettre tout le passage en majuscules |
C-09 · FAQ / Accordéon · faq
| Anatomie | <details>/<summary> natifs → question (H3 dans le summary) · réponse (--faq-answer-measure: 62ch) · filet de séparation |
| Variantes | faq--list (page FAQ, toutes ouvertes possibles) · faq--inline (3 questions en bas de page) · faq--single (une seule ouverte : name sur les details) |
| Tailles | question --faq-question-size (20-25 px) · réponse --tero-fs-body · --faq-pad-block: 24px |
| Tokens | --faq-* · --faq-transition (grid-template-rows, 200 ms) |
| États | ouvert/fermé : marqueur +/− ou chevron rotate 180deg sur --tero-dur-base ; chargement jamais (contenu statique) |
| Usage | la question est une vraie question d'utilisateur, pas un titre marketing · ordre = ordre de fréquence réelle · jamais d'accordéon pour du contenu principal (le contenu doit rester trouvable par un moteur et lisible sans JS) |
| A11y | <details>/<summary> natifs : état annoncé, clavier natif — ne pas réimplémenter · la réponse reste dans le DOM même fermée (lisible par les moteurs) · pas de FAQPage en JSON-LD si les questions ne sont pas visibles |
C-10 · Onglets · tabs
| Anatomie | role="tablist" → role="tab" (aria-selected, aria-controls) → role="tabpanel" (tabindex 0) |
| Variantes | tabs--underline (défaut : indicateur 2 px) · tabs--pills (sectionnement secondaire, rayon pilule) · tabs--filters (voir C-21) |
| Tailles | cible ≥ 44 px de hauteur · --tabs-pad-inline: 16px |
| Tokens | --tabs-* |
| États | sélectionné : --tabs-indicator + --tabs-fg-selected (couleur + indicateur, jamais la couleur seule) · focus : anneau |
| Usage | maximum 5 onglets ; jamais pour du contenu qui doit être indexé séparément (alors : des pages) · jamais un onglet qui cache le contenu principal sans équivalent HTML statique |
| A11y | motif ARIA tabs complet : flèches ←/→, Home/End, tabindex="-1" sur les onglets inactifs · si le JS ne charge pas, tous les panneaux restent visibles (dégradation obligatoire) |
C-11 · Fil d'Ariane · breadcrumb
| Anatomie | <nav aria-label="Fil d'Ariane"><ol> → items · séparateur / (aria-hidden) · dernier item = page courante (aria-current="page") |
| Variantes | breadcrumb--full (site du groupe : Tero > Pilier > Marque > Page) · breadcrumb--short (nom court de la page) |
| Tailles | --crumb-size (13-14 px), --crumb-max-lines: 2 — jamais tronqué |
| Tokens | --crumb-* |
| États | — |
| Usage | obligatoire sur les sous-marques : c'est un des mécanismes de rattachement au groupe (règle de la maison mère) · le nom court, jamais l'URL |
| A11y | aria-current="page" · la troncature par text-overflow a causé 20 mesures de coupure dans la référence : --crumb-max-lines: 2 et pas de max-width en ch sur mobile |
C-12 · En-tête collant et navigation principale (avec méga-menu) · header / nav / mega
| Anatomie | <header> → logo (tero + mention du pilier hors accueil groupe) · navigation (<nav aria-label="Navigation principale">) · actions (langue, réservation) · déclencheur de menu mobile · méga-menu (panneau --mega-*) |
| Variantes | header--transparent (sur hero, devient opaque au défilement) · header--solid · header--brand (sous-marque : logo de la marque + endossement Tero) |
| Tailles | --header-h: 72px → --header-h-compact: 56px après condensation · logo hauteur optique unifiée, jamais la hauteur native du fichier |
| Tokens | --header-, --nav-, --mega-* · --tero-z-header · --ui-image-overlay si transparent |
| États | scandé/condensé : hauteur + fond, --tero-dur-base · au défilement vers le haut seulement (jamais masquer l'en-tête en descendant ET en montant) · le logo reste toujours visible · focus : scroll-margin-top: --header-h sur toute cible d'ancre |
| Glassmorphism | autorisé uniquement ici, avec --header-fallback-bg opaque obligatoire en repli (@supports not (backdrop-filter)) |
| Usage | la maison mère est visible sur toute page de marque (mention + lien tero.be) · au maximum 6 entrées de premier niveau · le méga-menu n'accueille pas plus de 3 colonnes · un méga-menu qui ne contient que des liens = une simple liste déroulante |
| A11y | aria-expanded, aria-controls, fermeture par Échap, focus rendu au déclencheur, piège de focus dans le panneau, aria-current="page" sur l'entrée active · le méga-menu ne s'ouvre jamais au simple survol sans alternative clavier (WCAG 1.4.13 + 2.1.1) · en-tête collant : la cible focalisée ne doit jamais être recouverte (2.4.11) |
C-13 · Menu mobile · drawer
| Anatomie | déclencheur (icône + aria-label="Ouvrir le menu") → voile --overlay-bg → panneau --drawer-width → fermeture → liste de navigation → actions (réservation, langue, contact) |
| Variantes | drawer--side (droite, --tero-ease-drawer, 380 ms) · drawer--full (plein écran, sous-menus en accordéon natifs) |
| Tailles | --drawer-width: 22rem (jamais plus de 85 % de la largeur d'écran) · entrées ≥ 48 px de hauteur |
| Tokens | --drawer-*, --overlay-bg, --tero-z-drawer |
| États | ouvert : aria-expanded="true", focus piégé, overflow: hidden sur <body> sans provoquer de saut (compenser la barre de défilement) · fermé : focus rendu au déclencheur |
| Usage | le menu mobile contient tout ce que contient la navigation desktop (parité stricte : le contenu absent du menu mobile est un contenu caché, donc introuvable) · inclut l'endossement Tero et le contact |
| A11y | piège de focus réel · Échap ferme · aria-modal="true" si le panneau bloque le reste · cibles ≥ 44 px · jamais un menu accessible uniquement par <select>, jamais une entrée sans indication de sous-menu |
C-14 · Pied de page · footer
| Anatomie | ① bandeau de rappel (signature live. share. reconnect.) → ② 4 colonnes (Le groupe · Les marques · Adresses · Contact) → ③ mentions (TVA, B Corp, éditeur) → ④ liens légaux (confidentialité, cookies, accessibilité) → ⑤ sélecteur de langue |
| Variantes | footer--group (4 colonnes, arborescence complète) · footer--brand (2 colonnes + rattachement Tero) · footer--event (compact : contact, inscription, règlement) |
| Tailles | --footer-pad-block (profil normal) · --footer-columns: 4 → 1 colonne à 360 px |
| Tokens | --footer- · --brandbar- si le bandeau de marques y est intégré |
| États | — |
| Usage | les deux adresses (Belgique — Chau. de Tervuren 389, 1410 Waterloo, +32 484 59 87 48 · Luxembourg — Rives des Clausen 15, 2165 Luxembourg, +352 671 315 015) sont des données sourcées : jamais modifiées sans source · la signature est invariable, en minuscules, avec ses points · aucune mention d'agence comme éditeur du site (le nom de l'agence vit dans les mentions légales, pas dans le pied de page des données structurées) |
| A11y | <footer role="contentinfo"> · les liens légaux ont un libellé explicite (pas « ici ») · le sélecteur de langue utilise les noms de langues, jamais des drapeaux-emoji, et lang correct (fr-BE, nl-BE, en) |
C-15 · Bandeau des marques du groupe · brandbar
| Anatomie | <ul> de marques regroupées par pilier : Tero Hospitality · Tero Leisure · Tero Sport Events → logo ou nom + lien sortant |
| Variantes | brandbar--mono (défaut : logos en monochrome encre, couleur au survol) · brandbar--full (logos en couleur, page institutionnelle) · brandbar--wall (partenaires d'un événement — composant différent, C-27) |
| Tailles | --brandbar-logo-h: 32px hauteur optique unifiée (72 unités de tracé ≠ 72 px) · --brandbar-gap: 32px |
| Tokens | --brandbar-* · --tero-fs-meta pour les libellés |
| États | vide : jamais (le groupe a toujours 3 piliers) ; si une marque manque, on retire la rangée entière au lieu d'une rangée à trou |
| Usage | c'est le composant qui répond à la règle de la maison mère : l'accueil expose les trois piliers, les trois ou aucun · l'ordre est figé : Hospitality, Leisure, Sport Events · aucune marque sans son pilier, aucun lien sans domaine cible |
| A11y | chaque logo a un alt = nom officiel de la marque (ou aria-label sur le lien) · l'ensemble est une liste · la couleur des logos monochromes est portée par currentColor, jamais par un filtre CSS fragile |
C-16 · Carrousel · carousel
| Anatomie | piste (scroll-snap) → cartes (--carousel-gap) → boutons précédent/suivant (--carousel-btn-*) → pastilles de position → masque de débordement avec --carousel-peek |
| Variantes | carousel--scroll (défilement natif + snap, le défaut) · carousel--controls (boutons) · carousel--gallery (voir C-23) |
| Tailles | 1 à 3 cartes visibles · jamais plus de 3 · --carousel-btn-size: 44px |
| Tokens | --carousel- · --tero-z- si chevauchement |
| États | bouton désactivé en début/fin de piste (aria-disabled, contraste 4,54:1) · autoplay : interdit (--carousel-autoplay: 0) : WCAG 2.2.2 impose une pause, et l'autoplay dégrade l'INP |
| Usage | un carrousel n'est jamais le seul moyen d'accéder à une information : l'index complet est aussi une page · jamais un carrousel infini pour de la preuve sociale · sur mobile, le défilement natif est préféré aux boutons |
| A11y | role="region" + aria-label · scroll-snap navigable au clavier (les cartes sont focalisables, donc le défilement suit) · les pastilles sont des boutons avec libellé (« Aller à la carte 3 sur 8 ») · prefers-reduced-motion : scroll-behavior: auto |
C-17 · Champs de formulaire · field
| Anatomie | <label> visible au-dessus (jamais un placeholder-label) → champ (--field-) → indication (--field-hint-) → message d'erreur (aria-describedby) |
| Variantes | texte · courriel · téléphone (type="tel" + inputmode) · nombre · zone de texte · liste déroulante native (<select>) · choix multiple · date (<input type="date">) |
| Tailles | hauteur ≥ 44 px · font-size: max(1rem, 1em) (16 px minimum : anti-zoom iOS) · --field-pad-block: 12px |
| Tokens | --field-*, --ui-border-strong (4,54:1 — jamais #D9D9D9), --ui-fg-muted pour le placeholder (C8) |
| États | focus : anneau --ui-focus (jamais un changement de couleur de bordure seul) · invalide : bordure 2 px --ui-danger + fond --ui-danger-bg + message · désactivé : --ui-disabled-fg sur --ui-surface-alt · lecture seule : bordure en pointillés, texte en --ui-fg |
| Usage | le minimum de champs : un formulaire de contact à 7 champs se remplit une fois sur deux, à 4 champs trois fois sur quatre (ordre de grandeur du secteur) · autocomplete renseigné sur tous les champs (2.5.x + gain de conversion) · required HTML et validation serveur · jamais de validation au premier caractère |
| A11y | label lié par for/id (1.3.1, 3.3.2) · erreur avec cause et correction (3.3.1, 3.3.3) · aria-invalid="true" · le message est lié par aria-describedby · le placeholder ne remplace jamais le label · zone de saisie ≥ 44 px · aucune information portée par la seule couleur de bordure |
C-18 · Sélecteur de pays · country-select
| Anatomie | <select> natif (groupe d'<option>) ou liste de boutons radio présentés en grille ; jamais une liste personnalisée en div |
| Variantes | country-select--native (défaut : natif, accessible, clavier gratuit) · country-select--radio (2 à 5 pays seulement) |
| Tailles | hauteur ≥ 44 px · libellé « Belgique (BE) », « Luxembourg (LU) », « Pays-Bas (NL) », « France (FR) » |
| Tokens | --field-* |
| États | invalide : identique à C-17 · aucune sélection par défaut si le choix est signifiant (jamais « Belgique » présélectionnée pour un visiteur néerlandais) |
| Usage | le nom du pays en clair, jamais un drapeau seul (un drapeau est ambigu et non traduit) · l'indicatif téléphonique suit le pays choisi sans effacer ce que l'utilisateur a déjà saisi · jamais de liste de 250 pays sans saisie filtrante |
| A11y | <select> natif = clavier et lecteur d'écran gratuits · si <datalist> : autocomplete="country-name" · lang inchangé (les noms de pays restent dans la langue de la page) |
C-19 · Case de consentement · consent-checkbox
| Anatomie | <input type="checkbox"> + <label> contenant le texte de consentement + lien vers la politique de confidentialité → zone cliquable ≥ 44 × 44 px |
| Variantes | consent--required (envoi bloqué sans coche) · consent--optional (marketing, décochée par défaut) · consent--double (deux cases distinctes : exécution et prospection) |
| Tailles | carré --field-checkbox-size: 20px, zone cliquable 44 px (padding-block sur le label) |
| Tokens | --field-*, --ui-border-strong pour la case non cochée (3:1 exigé : c'est un composant d'interface) |
| États | coché : coche vectorielle + couleur (jamais la couleur seule) · erreur : message lié + aria-invalid · désactivé : --ui-disabled-fg |
| Usage | jamais de case pré-cochée pour une finalité marketing · une seule demande de consentement par finalité · le refus est aussi simple que l'acceptation (2 clics max) · le texte dit ce à quoi on consent, pas « J'accepte les conditions » |
| A11y | le <label> entoure le texte → zone cliquable conforme · l'erreur est annoncée (3.3.1) · la case est atteignable au clavier, cochable par Espace (natif) |
C-20 · Messages d'erreur et notifications · error / toast
| Anatomie | Erreur de champ : texte sous le champ, lié, cause + correction · Erreur de formulaire : résumé en tête, role="alert", ancres vers les champs · Notification : --toast-bg/fg, fermeture, durée |
| Variantes | toast--success, toast--info, toast--warning, toast--error · banner--cookie (en flux, jamais superposé aux CTA) |
| Tailles | --toast-width: 22rem · --toast-duration: 6000ms (les erreurs ne disparaissent pas : fermeture manuelle) |
| Tokens | --toast-*, --ui-danger, --ui-success, --ui-info · --tero-shadow-float |
| États | entrée/sortie : opacity + translateY ≤ 8 px, --tero-dur-base · réduction de mouvement : apparition sans translation |
| Usage | le message d'erreur dit ce qui est faux et comment le corriger (« Le téléphone doit être au format +32 4XX XX XX XX ») — jamais « Erreur 422 » · --ui-danger = terracotta (5,77:1) · un message d'avertissement n'est jamais du jaune sur blanc (sable = 1,44:1 : icône + texte encre) |
| A11y | role="alert" pour l'erreur bloquante, role="status" (aria-live="polite") pour le succès · le toast ne vole pas le focus · le message reste lisible sans couleur (icône + texte) · jamais plus d'une notification empilée à l'écran |
C-21 · Tableaux · table
| Anatomie | <caption> → <thead> (<th scope="col">) → <tbody> → <tfoot> si totaux |
| Variantes | table--simple (filets horizontaux, sans fond) · table--compare (lots, tarifs) · table--responsive (devient une liste de paires en HTML, pas par CSS) |
| Tailles | cellule --table-cell-pad-* · en-tête --table-head-size (13-14 px) · jamais de texte sous 12 px |
| Tokens | --table-* |
| États | vide : ligne de message + CTA · chargement : squelette de lignes |
| Usage | les tableaux se réorganisent en pile à 360 px, jamais en défilement horizontal caché (règle groupe § 8.4) · un tableau de tarifs n'affiche jamais plus de 3 offres · l'unité est toujours affichée (« / heure », « / pers. ») |
| A11y | <caption> (3.1.5 ou texte visible) · scope sur les en-têtes · jamais de <table> pour la mise en page · les montants sont alignés à droite avec tabular-nums et lus correctement |
C-22 · Galerie photo · gallery
| Anatomie | grille de vignettes (--gallery-gap, ratio imposé) → visionneuse (lightbox) → légende → commandes (précédent, suivant, fermer, compteur) |
| Variantes | gallery--grid (mosaïque statique) · gallery--carousel (C-16) · gallery--lightbox (ouverture plein écran) |
| Tailles | vignettes srcset 480/768/1024/1440 · images ≤ 90 Ko pour une carte, ≤ 200 Ko pour un hero |
| Tokens | --gallery-* · --overlay-bg · --tero-z-modal |
| États | chargement : width/height + fond --ui-surface-alt (aucun décalage) · image absente : le cadre garde son ratio, le alt reste lisible |
| Usage | photos réelles des lieux du groupe uniquement · vérifier les enseignes visibles avant d'attribuer une photo à un lieu · jamais de photo de stock générique à la place d'un vrai lieu (piège n° 7) |
| A11y | lightbox = dialogue modal : role="dialog", aria-modal, Échap, piège de focus, focus rendu · chaque photo porte un alt réel (jamais « image » ni le nom de fichier) · les commandes ont des libellés, pas seulement des icônes |
C-23 · Filtres · filters
| Anatomie | groupe de <fieldset> + <legend> → cases à cocher ou puces (--filter-chip-*) → compteur de résultats (role="status") → bouton « Effacer les filtres » |
| Variantes | filters--chips (en ligne) · filters--sidebar (jamais vide : si aucun filtre actif, la colonne porte une phrase de cadrage + une porte de sortie) · filters--select (mobile) |
| Tailles | puce ≥ 36 px de haut, cible 44 px · regroupement par axe (lieu, activité, public, prix) |
| Tokens | --filter-* |
| États | actif : apli --filter-chip-bg-active + coche (jamais la couleur seule) · aucun résultat : message + action (« Élargir à toute la Belgique ») — jamais « Aucun résultat. » · chargement : compteur « Chargement… » |
| Usage | le nombre de résultats est annoncé avant le clic quand c'est possible · les filtres ne masquent jamais la totalité du contenu sans que l'URL soit partageable · jamais de filtre par couleur seule |
| A11y | <fieldset>/<legend> (1.3.1) · role="status" sur le compteur · aria-pressed/aria-checked corrects · ordre de tabulation de gauche à droite, pas par colonne |
C-24 · Pagination · pagination
| Anatomie | <nav aria-label="Pagination"> → Précédent · numéros · Suivant · compteur « Page 2 sur 7 » |
| Variantes | pagination--numbered (pages SEO-indexables) · pagination--loadmore (chargement progressif avec URL de repli) · pagination--infinite (interdit — le pied de page devient inatteignable) |
| Tailles | cible ≥ 44 px · --pagination-size: 44px |
| Tokens | --pagination-* |
| États | page courante : aria-current="page" + --pagination-border-current (jamais la couleur seule) · premier/dernier : Précédent/Suivant désactivés et annoncés |
| Usage | chaque page est une URL distincte, indexable, avec rel="prev"/rel="next" ou un <link> équivalent · jamais de pagination uniquement en JavaScript sans URL |
| A11y | <nav> étiqueté · aria-current="page" · les numéros ont un libellé complet (« Page 3 », pas « 3 ») |
C-25 · Bloc de réservation / barre d'action · booking
| Anatomie | conteneur → sélecteur de lieu/date → action principale → mention de confiance (« Annulation gratuite 24 h avant » — si sourcé) |
| Variantes | booking--inline (dans une fiche) · booking--bar (barre collante mobile) · booking--external (sortie vers la plateforme du groupe) |
| Tailles | barre mobile hauteur ≥ 56 px, marge basse env(safe-area-inset-bottom) |
| Tokens | --btn-, --field-, --ui-action-bg (CTA noir au niveau groupe : c'est la mesure) |
| États | chargement : aria-busy sur le conteneur + libellé « Vérification… » (jamais un spinner seul) · indisponible : message + alternative (téléphone du lieu, source) |
| Usage | le lien de réservation sortant porte les UTM du groupe · position: sticky et non fixed pour ne jamais recouvrir un CTA ni le pied de page · jamais un formulaire de réservation à 6 étapes |
| A11y | la barre collante ne masque jamais un élément focalisé (2.4.11) · le changement de contexte est annoncé · les messages d'erreur suivent C-20 |
C-26 · Composants de second niveau (spécification allégée)
| Composant | Rôle | Tokens | Point d'accessibilité critique |
|---|---|---|---|
badge / chip | étiquette d'état, d'édition, de catégorie | --ui-accent-soft, --ui-fg | contraste ≥ 4,5:1 ; jamais une couleur seule pour un statut |
price | prix et unité | --stat-*, tabular-nums | l'unité est dans le HTML, jamais dans une image ; source obligatoire |
testimonial | avis client | --quote-* | verbatim + nom + société ; 3 à 6 avis maximum |
partner-wall | logos de partenaires | --brandbar-* | chaque logo a un alt = nom du partenaire ; « Ils nous font confiance » interdit |
map | carte des lieux | --ui-border, --radius-media | équivalent textuel obligatoire (liste des lieux avec adresses) |
share | partage | --btn-3-* | pas de script tiers bloquant ; les URL de partage n'ouvrent pas d'onglet sans l'annoncer |
newsletter | inscription | --field-* | consentement explicite (C-19) ; pas de double pop-up |
modal | dialogue | --tero-shadow-float, --tero-z-modal | role="dialog", aria-modal, piège de focus, Échap, focus rendu |
tooltip | infobulle | --toast-* | jamais porteur d'une information exclusive (1.4.13) ; survol et focus |
skip-link | lien d'évitement | --ui-focus | premier arrêt de tabulation, visible à la prise de focus |
lang-switch | sélecteur de langue | --link-* | noms de langues en clair, hreflang, jamais d'emoji-drapeau |
video | lecteur | --radius-media | sous-titres si parole ; prefers-reduced-motion → poster seul |
timeline | histoire du groupe (2020, 2022…) | --stat-*, --ui-border | chaque date est sourcée ; liste ordonnée <ol> |
cookie-banner | consentement | --toast-* | en flux ou avec hauteur réservée, jamais superposé à un CTA (recouvrement mesuré 42-89 % dans la référence) ; une seule demande, refusable en un clic |
---
6. GABARITS DE PAGES — wireframes textuels et spécifications
Conventions de lecture : [H1] titre ; (→) filet de 1 px ; [===] data-field="deep" ;
[~~~] data-field="light-b" ; [CTA] action ; [SRC] donnée qui exige une source.
Règles communes : un seul H1 · skip-link en premier arrêt · 8 à 11 sections maximum sur
un gabarit riche · jamais deux fonds identiques adjacents · `scroll-padding-block-start:
--header-h` · le contenu est visible sans JavaScript.
G-01 · Accueil groupe (tero.be)
[en-tête : tero | 3 piliers | Contact | [CTA] Nous contacter] [HERO pleine largeur — photo de lieu réel + voile] sur-titre « GROUPE TERO » [H1] « Arrêter le temps, pour se reconnecter aux autres, à la nature, à soi. » [SRC] chapô (≤ 58 ch) · [CTA principal] · [CTA secondaire] [~~~ BANDE DE CHIFFRES : ~125 personnes · 17 lieux · 25 ans · 2020 / 2022 [SRC]] [LES TROIS PILIERS — 3 cartes, une par pilier, ordre figé] Hospitality · Leisure · Sport Events (les trois, ou aucun) (→) [PASSAGE CITABLE — 2-3 phrases + 1 chiffre, un seul par section] [GRILLE DE LIEUX — 6 entités max, ratio 7/5 puis 8/4] [=== BANDE PROFONDE — le groupe : valeurs (passion · engagement · respect · authenticité)] [FAQ — 3 à 5 questions « qui est Tero ? »] [BANDEAU DES MARQUES DU GROUPE — par pilier, liens sortants] [pied : 2 adresses [SRC] · signature live. share. reconnect. · langue]
| Spécification | Valeur |
|---|---|
| Répond à la question « qui est Tero ? » | dans les 2 premiers écrans (règle groupe § 6.3.4) |
| JSON-LD | Organization Tero Group avec subOrganization (les 3 piliers) · logo = mot-symbole, jamais le monogramme · sameAs = réseaux officiels uniquement (jamais une URL de prévisualisation) |
| Interdit | un pilier mis en avant au détriment des deux autres · un nœud Article sur cette page · un auteur « agence » |
| Piège mesuré | Organization.logo = glyphe 1 272 × 1 861 (portrait) : c'est le défaut n° 6 à ne pas reproduire |
G-02 · Page pilier (Hospitality · Leisure · Sport Events)
[en-tête avec rattachement : Tero > pilier] [HERO — titre du pilier + chapô + une photo qui dit le pilier] [~~~ DÉFINITION DU PILIER — 2 paragraphes, chiffres sourcés [SRC]] [LES MARQUES DU PILIER — carte par marque, lien vers sa page] (→) [=== BANDE PROFONDE — ce que le pilier apporte, une citation de dirigeant] [ACTUALITÉS / ÉVÉNEMENTS du pilier] [CTA — rejoindre, réserver, candidater] [pied]
| Spécification | Valeur |
|---|---|
| H1 | « Tero Hospitality », etc. — orthographe exacte, jamais abrégée, jamais traduite |
| Rattachement | fil d'Ariane Tero > Pilier + lien vers tero.be |
| JSON-LD | WebPage + isPartOf Tero Group · about = le pilier |
| Interdit | une page de pilier sans lien vers les marques du pilier · un chiffre du pilier repris du groupe (doublon) |
G-03 · Page marque (teropadelclub.be, terostudio.be, garisart.be…)
[en-tête : logo de la marque + « une marque du groupe Tero » + lien tero.be] [HERO — identité de la marque, accent = --ui-accent du thème] [preuve : chiffres de la marque [SRC]] [~~~ OFFRES / SERVICES — cartes, ratio alterné] (→) [LIEUX OU CLUBS de la marque — grille] [=== BANDE PROFONDE — rattachement au groupe + les autres marques] [FAQ marque] [CTA principal — réservation ou contact] [pied : adresse de la marque + adresses du groupe]
| Spécification | Valeur |
|---|---|
| Thème | data-theme="<marque>" — seuls accent, accent-on, accent-text, deep, focus changent (§ 4.2) |
| Rattachement | mention + lien tero.be obligatoires (règle de la maison mère § 6.3.2) |
| JSON-LD | Organization (marque) avec parentOrganization = Tero Group · logo de la marque |
| Interdit | une sous-marque présentée sans rattachement (piège n° 7) · un lockup inventé « tero <marque> » |
G-04 · Fiche lieu
[fil d'Ariane : Tero > pilier > marque > lieu] [H1] nom du lieu + ville + pays [GALERIE — 3 à 6 photos réelles, enseignes vérifiées] [~~~ INFORMATIONS FACTUELLES [SRC] : adresse · horaires · capacité · accès · téléphone] [CTA] Itinéraire · Réserver · Appeler (→) [CE QU'ON Y FAIT — activités liées (cartes)] [=== BANDE PROFONDE — citation ou chiffre du lieu] [LIEUX À PROXIMITÉ — 3 cartes] [pied]
| Spécification | Valeur |
|---|---|
| JSON-LD | Place (ou Restaurant si applicable) + address PostalAddress + geo si source |
| Données sensibles | adresse, horaires, capacité, téléphone : jamais corrigés sans source |
| Piège | un aggregateRating sans avis vérifiables · une photo attribuée au mauvais lieu |
| A11y | la carte géographique a un équivalent textuel (adresse + lien itinéraire) |
G-05 · Fiche activité
[H1] nom de l'activité [chapô — ce que c'est, en 2 phrases] [=== BANDE DE CHIFFRES : durée · niveau · âge minimum · nombre de participants [SRC]] [FORMULES ET TARIFS — table simple, unité affichée, 3 offres max] [GALERIE — photos d'activité réelle] [LIEUX OÙ PRATIQUER — cartes de lieux] [FAQ — 4 à 6 questions pratiques] [CTA] Réserver · Nous écrire
| Spécification | Valeur |
|---|---|
| JSON-LD | Service ou Product selon la nature · offers uniquement avec prix et devise sourcés |
| Usage | l'unité de prix est toujours affichée (« / heure », « / pers. ») · la durée est un format normalisé |
| Piège | un prix affiché deux fois, deux unités différentes dans la même page |
G-06 · Fiche restaurant (Bistro 17, Bistro Pilko, Knokke Out Zoute, Teroir)
[H1] nom + type de cuisine [galerie plats / salle — photos réelles] [INFORMATIONS [SRC] : adresse · service · réservation · capacité de groupe] [CTA] Réserver une table [CARTE / MENU — extraits, prix sourcés] [=== BANDE PROFONDE — la table partagée, la signature du groupe] [ÉVÉNEMENTS PRIVÉS — lien team building] [pied]
| Spécification | Valeur |
|---|---|
| JSON-LD | Restaurant + servesCuisine + openingHoursSpecification (source obligatoire) + menu |
| Interdit | aggregateRating, un avis inventé, une photo de stock de plat |
| Piège | un tarif de menu sans date de mise à jour |
G-07 · Événement (Immorun, Batirun, BPP26, Tero Trail…)
[en-tête événementiel : logo IMMORUN + « un événement du groupe Tero » + lien tero.be] [HERO — date, lieu, distance/discipline, [CTA] S'inscrire, compte à rebours] [=== BANDE DE CHIFFRES : édition · participants · distance · dénivelé [SRC]] [PARCOURS / PROGRAMME — onglets ou tableaux] [INSCRIPTION ET TARIFS — table, unité, échéances] [PARTENAIRES — mur de logos (composant partenaire, C-26)] [CO-MARQUE — emplacement séparé, hauteur optique unifiée (co-marque IMMOBEL : jamais dans le bloc Tero)] [FAQ — dossard, retrait, stationnement, météo] [pied : contact organisateur + mentions]
| Spécification | Valeur |
|---|---|
| Thème | data-theme="<event>" + data-theme-scope="event" (§ 4.6) |
| JSON-LD | Event + organizer = Tero (ou la marque) + location + offers sourcés · jamais Article |
| Après l'édition | le thème événementiel est retiré de la CSS ; la page devient un compte rendu avec les résultats |
| Piège | un compte à rebours en JavaScript bloquant · une inscription à 6 étapes |
G-08 · Article (éditorial, actualité)
[fil d'Ariane] [H1] titre · [date + auteur réel + temps de lecture] [CHAPÔ — 2 phrases autonomes] [image de tête — ratio 16/9, légende] [corps — H2/H3, listes, citations, mesure de lecture 66 ch] [ENCADRÉ — passage citable, un chiffre] [=== BANDE PROFONDE — CTA de fin d'article] [ARTICLES LIÉS — 3 cartes]
| Spécification | Valeur |
|---|---|
| JSON-LD | Article/NewsArticle sur une page éditoriale uniquement · author = personne réelle identifiée · datePublished / dateModified réels |
| Interdit mesuré | Article sur une page non éditoriale · auteur lumisys (agence) · sameAs de prévisualisation Infomaniak |
| Piège | un titre de plus de 60 caractères SEO, une méta-description recopiée du chapô sans valeur |
G-09 · FAQ
[H1] Questions fréquentes + intro 2 phrases [SOMMAIRE — ancres vers les thèmes si > 8 questions] [GROUPES DE QUESTIONS — <details>/<summary> natifs, 6 à 12 par groupe] (→) [CTA — « Vous ne trouvez pas ? Écrivez-nous » + contact réel]
| Spécification | Valeur |
|---|---|
| JSON-LD | FAQPage uniquement si toutes les questions sont visibles et réelles — jamais de FAQ inventée pour le référencement |
| Usage | réponse ≤ 62 ch de mesure · questions en 500 · ordre = fréquence réelle |
| Piège | une FAQ avec 30 questions, aucune réponse à la question du visiteur (contact, prix, accès) |
G-10 · Contact
[H1] Nous contacter [=== BANDE D'ADRESSES — Belgique [SRC] · Luxembourg [SRC] + téléphones] [FORMULAIRE — 4 à 6 champs maximum · consentement (C-19) · erreurs (C-20)] [CANAUX DIRECTS — téléphone, courriel, réservation, presse] [CARTE — avec équivalent textuel] [HORAIRES DE RÉPONSE [SRC]]
| Spécification | Valeur |
|---|---|
| Aucune donnée de contact inventée | deux adresses, deux téléphones, un seul courriel par usage (source obligatoire) |
| JSON-LD | ContactPage + Organization (adresses) |
| A11y | les champs suivent C-17/C-18/C-19 ; l'erreur dit la correction attendue |
G-11 · À propos / Histoire
[H1] L'histoire du groupe [CHAPÔ — l'idée : arrêter le temps [SRC]] [=== FRISE — 2020 création · 2022 rapprochement (Arthur et Nicolas Lhoist) [SRC] · 25 ans d'expertise] [LES TROIS PILIERS — rappel] [LES VALEURS — passion · engagement · respect · authenticité] [=== DÉMARCHE B CORP — statut, preuves, date [SRC]] [LES DIRIGEANTS — portraits réels, verbatims] [CTA — presse / carrières]
| Spécification | Valeur |
|---|---|
| JSON-LD | AboutPage + Organization (foundingDate sourcé — jamais deviné) · founder = personnes réelles |
| Interdit | une date, un chiffre ou une distinction sans source · une photo de dirigeant non fournie |
G-12 · Impact (B Corp, RSE)
[H1] Notre impact [chapô — la démarche, sans superlatif] [=== BANDE DE CHIFFRES d'impact [SRC] : chaque indicateur avec son périmètre et son année] [ENGAGEMENTS — 3 à 5, chacun avec un indicateur et une échéance] [PREUVES — certifications, rapports téléchargeables, date] [LIMITES — ce qui n'est pas encore fait (crédibilité)] [CTA — télécharger le rapport / nous écrire]
| Spécification | Valeur |
|---|---|
| Règle | un indicateur sans périmètre ni année est une affirmation invérifiable : interdit |
| Interdit | « leader », « unique », « exceptionnel » sans preuve (règle éditoriale) ; un badge B Corp affiché avant certification |
G-13 · Carrières
[H1] Travailler chez Tero [chapô — missions, périmètre, chiffres de l'équipe [SRC]] [=== BANDE — les 3 piliers et leurs métiers] [OFFRES — liste filtrable (C-23), chaque offre avec lieu, contrat, date] [PROCESSUS DE RECRUTEMENT — 3 à 4 étapes, durées] [CTA] Candidature spontanée (formulaire court)
| Spécification | Valeur |
|---|---|
| JSON-LD | JobPosting par offre avec datePosted, employmentType, jobLocation — retiré à la clôture |
| A11y | chaque offre est un lien avec un titre complet (« Coach padel — Waterloo — CDI ») |
| Piège | des offres expirées encore en ligne (données structurées périmées) |
G-14 · Landing (campagne, acquisition)
[en-tête réduit — logo + endossement Tero + téléphone] [HERO — promesse claire + [CTA] unique] [PREUVE — 3 chiffres sourcés ou 3 logos partenaires] [BÉNÉFICES — 3 blocs, un argument par bloc] [OBJECTIONS — FAQ courte (3 questions)] [CTA FINAL — même action que le hero, libellé identique] [pied réduit]
| Spécification | Valeur |
|---|---|
| Une seule action | le même libellé du début à la fin ; deux CTA concurrents = échec |
| JSON-LD | WebPage (pas Article) · Organization en about |
| Piège | une landing sans navigation ni adresse : le visiteur ne peut pas vérifier qui parle |
G-15 · 404
[H1] Cette page n'existe plus [1 phrase : ce qui a probablement changé] [LIENS UTILES — 4 maximum : accueil, marques, lieux, contact] [RECHERCHE — champ (C-17) prérempli si possible]
| Spécification | Valeur |
|---|---|
| Réponse HTTP | 404 réel (jamais 200 avec un contenu d'erreur : c'est un piège SEO mesuré) |
| Usage | les anciennes URL redirigées en 301 arrivent ici uniquement si la cible n'existe pas |
| A11y | pas d'auto-redirection, pas de compte à rebours |
G-16 · Recherche
[H1] Recherche [CHAMP de recherche (C-17) + filtres (C-23)] [COMPTEUR DE RÉSULTATS — role="status"] [RÉSULTATS — cartes (C-03) ou lignes, terme surligné (jamais la couleur seule)] [PAGINATION (C-24) avec URL par page] [AUCUN RÉSULTAT — 3 suggestions de reformulation + portes de sortie]
| Spécification | Valeur |
|---|---|
| URL | ?q= partageable, indexable en noindex sur les pages de résultats |
| A11y | role="status" sur le compteur · le champ garde focus après filtrage · l'état vide propose une action |
| Piège | un surlignage par couleur de fond sans contraste calculé ; une recherche vide qui affiche tous les contenus |
---
7. PATTERNS TRANSVERSES
7.1 Grille
| Élément | Règle |
|---|---|
| Colonnes | 12 · gouttière --tero-gutter (16→24 px) · conteneur 1 100 px (cf. C23) |
| Ratios autorisés | 12/0 · 7/5 · 8/4 · 5/7 · 9/3 · 4/4/4 · 3/3/3/3 — jamais deux sections voisines au même ratio |
| Asymétrie | au moins 2 ratios de colonnes différents par page |
| Débordement | 1 à 2 éléments traversent la gouttière (décalage 24-32 px, 15-20 % de la hauteur) — au plus une section sur trois |
| Colonne latérale | jamais vide : phrase de cadrage + porte de sortie utile |
| Alignement | majoritairement à gauche · 1 seul bloc centré par page |
| Points de rupture | 360 / 390 / 768 / 1024 / 1440 · zéro débordement horizontal à 360 px |
7.2 Rythme et densité
| Élément | Règle | Valeur |
|---|---|---|
| Profils de section | exclure, resserré, normal, ample | --tero-rhythm-flush/tight/normal/loose (32→44 / 44→64 / 60→96 / 72→120 px) |
| Alternance | jamais deux profils identiques adjacents ; un profil « ample » au maximum tous les 4 blocs | règle |
| Entre entrées éditoriales | 2,5 à 3,5 rem — c'est la gouttière qui sépare, pas une boîte | --tero-space-entries |
| Densité | part de surface portant du contenu réel | ≥ 70 % sain · 40-60 % acceptable · < 25 % à refaire |
| Ancres | scroll-padding-block-start = hauteur d'en-tête + marge | --header-h + 1,5 rem |
| Vide intentionnel | --tero-rhythm-loose est le seul vide autorisé | règle |
7.3 Hiérarchie typographique
| Niveau | Taille | Graisse | Rôle |
|---|---|---|---|
| Sur-titre | label | 600 capitales, +0.18em | contexte (« GROUPE TERO », « LIEU ») |
| H1 | display | 700 | un seul par page, dans le hero |
| H2 | title-l | 600 | section |
| H3 | title-m / title-s | 500-600 | entité, sous-section |
| H4 | title-s | 500 | sous-partie d'une carte ou d'un encart |
| Chapô | lead | 400 | 2 phrases autonomes, ≤ 58 ch |
| Corps | body | 400 | ≤ 66 ch |
| Mention | meta | 400 | date, source, légende |
| Chiffre | stat | 700, tabular-nums | jamais une pastille |
Interdits : deux familles dans un même titre · plus d'un mot en script par page (et zéro
tant que WildYouth n'est pas arbitrée) · capitales sur plus de 2 lignes · une taille hors rampe.
7.4 Composition
- Alterner les fonds : blanc →
#F6F4F4→ profond → accent. Jamais deux identiques côte à côte. - Varier la position du titre de section : à gauche, puis colonne latérale, puis bandeau centré.
Jamais huit en-têtes centrés d'affilée.
- Une idée par section. Une section qui porte deux idées en porte zéro.
- Le contenu décide de la grille, jamais l'inverse.
- Le titre porte la carte : pas de fond, pas de bordure, pas d'ombre sur une carte de contenu.
- La preuve avant la promesse : un chiffre, un lieu, une date avant un adjectif.
7.5 Responsive
| Seuil | Règle |
|---|---|
| 360 px | aucune information retirée (on réorganise, on ne supprime pas) · aucun débordement horizontal · tableaux en pile |
| 390 px | capture de contrôle obligatoire |
| 768 px | passage 2 colonnes · méga-menu → menu mobile |
| 1024 px | grille complète, méga-menu desktop |
| 1440 px | conteneur centré, contenu borné (jamais des lignes de 140 caractères) |
| Pointeur | tout :hover est gardé par @media (hover:hover) and (pointer:fine) + équivalent :focus-visible de même intensité |
| Saisie | font-size: max(1rem, 1em) · jamais user-scalable=no |
| Encoches | viewport-fit=cover + env(safe-area-inset-*) |
| Hauteur | 100svh/100dvh, jamais 100vh |
| Images | srcset 480/768/1024/1440/1920 + sizes explicite + width/height |
| Vidéo | deux sources distinctes (portrait 9:16 mobile, paysage 16:9 desktop), chacune avec poster |
7.6 Mouvement
| Usage | Durée | Courbe |
|---|---|---|
| Feedback (couleur, opacité, soulignement) | --tero-dur-fast 140 ms | --tero-ease-out-strong |
| Survol, état de bouton | --tero-dur-base 200 ms | --tero-ease-out-strong |
| Onglet, panneau, changement d'état | --tero-dur-move 240 ms | --tero-ease-out-strong |
| Tiroir, menu mobile | --tero-dur-draw 380 ms | --tero-ease-drawer |
| Révélation au défilement | --tero-dur-slow 680 ms | --tero-ease-out-strong |
| Sortie | 60 à 70 % de la durée d'entrée | courbe d'entrée inversée |
Interdits de mouvement : animer width, height, top, left, box-shadow · plus de
3 courbes sur tout le site · un ease écrit à la main dans un composant · plus de 3 éléments
animés simultanément · une translation d'entrée > 24 px · un zoom d'image hors 1.02-1.05 ·
bounce/elastic sur de l'UI fonctionnelle · une bibliothèque d'animation externe ·
un loader plein écran bloquant · une révélation lettre par lettre · du scroll-jacking ·
une révélation indispensable à la visibilité du contenu (sans @supports, le contenu doit
rester visible : c'est un piège mesuré sur Firefox stable).
/* Neutralisation globale : un seul endroit, pas 25 composants. */
@media (prefers-reduced-motion: reduce) {
:root { --tero-dur-fast: 1ms; --tero-dur-base: 1ms; --tero-dur-move: 1ms;
--tero-dur-draw: 1ms; --tero-dur-slow: 1ms; }
*, ::before, ::after { animation-duration: 1ms !important; animation-iteration-count: 1 !important;
transition-duration: 1ms !important; scroll-behavior: auto !important; }
video[autoplay] { display: none; } /* on sert le poster */
}
7.7 Médias
| Type | Règle |
|---|---|
| Format | AVIF puis WebP, repli JPEG/PNG · jamais un PNG de photo |
| Poids | hero 2 400-3 840 px ≤ 200 Ko · section 1 600-1 920 px ≤ 140 Ko · carte 800-1 200 px ≤ 90 Ko · vignette 160-400 px ≤ 20 Ko |
| Nommage | <marque>-<sujet>-<contexte>[-<variante>].<ext>, minuscules, sans accent, jamais IMG_2841.jpg |
| Attributs | width + height (ou aspect-ratio) · alt réel · image LCP : eager + fetchpriority="high" + preload |
| Contenu | photos réelles des lieux du groupe · enseignes vérifiées avant attribution · jamais de stock générique à la place d'un vrai lieu |
| Vidéo | 6-12 s, boucle invisible, -an, faststart · desktop ≤ 4 Mo, mobile ≤ 2,5 Mo (découpe portrait dédiée) |
| Traitement | --ui-image-treatment (thème) · jamais de filtre qui dégrade la reconnaissance d'un lieu |
7.8 Langues (FR / NL / EN)
- Parité de structure stricte : les trois langues ont la même arborescence, les mêmes blocs,
le même nombre de sections. Un bloc présent en FR et absent en NL est un défaut.
- Adaptation native, jamais une traduction mot à mot · NL = néerlandais belge ·
EN = anglais britannique.
- Noms propres jamais traduits : Tero Group, Tero Hospitality, Teroir, Knokke Out,
Bistro Pilko, Immorun, Batirun.
- Le sélecteur de langue affiche des noms de langues, jamais des emoji-drapeaux.
langcorrect par page :fr-BE,nl-BE,en·hreflangréciproque +x-default.- Prix et formats locaux :
1.234,56 €en FR/NL,€1,234.56en EN · dates au format local. - Les composants ne fixent jamais une largeur en
chsur un libellé traduit : les chaînes
NL sont 15 à 30 % plus longues. Les gabarits sont testés dans les trois langues (§ 8.3).
7.9 États globaux de la page
| État | Règle |
|---|---|
| Chargement initial | le contenu critique est dans le HTML (pas de coquille rendue en JS) · pas de loader plein écran |
| Erreur réseau | message + action « Réessayer » · jamais une page blanche |
| JavaScript absent | navigation, FAQ, onglets et filtres restent utilisables (dégradation obligatoire) |
| Hors ligne | l'information factuelle (adresse, téléphone) reste consultable si mise en cache |
prefers-contrast: more | filets → --ui-border-strong, encres secondaires → --ui-fg |
forced-colors: active | outline-color: Highlight · les aplats de marque sont remplacés par les couleurs système : l'information ne doit jamais dépendre d'un aplat |
| Impression | contenu sur blanc, liens avec URL développée, navigation masquée |
---
8. CONTRATS DE VÉRIFICATION
8.1 Budgets opposables
| Ressource | Budget | Mesure de référence |
|---|---|---|
| CSS du socle (couches 1 à 3) | ≤ 45 Ko gzip | l'existant : 536 feuilles, 9,1 Mo |
| CSS d'un thème de marque | ≤ 3 Ko gzip | — |
| Nombre de fichiers CSS servis | ≤ 4 | 536 |
| Polices | ≤ 5 fichiers .woff2 (400/500/600/700/900) · ≤ 2 préchargés | 5 fichiers référencés |
| JS livré | ≤ 50 Ko gzip, aucune bibliothèque d'animation | référence : 0 Ko externe |
| Couleurs distinctes par page | ≤ 12 (8 neutres + 3 de marque + 1 d'état) | 164 valeurs distinctes sur le site |
| Tailles de texte par page | ≤ 9 (la rampe) | 30 tailles distinctes |
| Rayons distincts par page | ≤ 4 | 15+ |
| Durées distinctes par page | ≤ 6 (les tokens) | aucune échelle |
| LCP / INP / CLS | < 1,5 s / < 200 ms / < 0,1 | cible groupe § 11 |
8.2 Contrôles automatiques (bloquants en intégration continue)
| # | Contrôle | Méthode | Seuil |
|---|---|---|---|
| V1 | Aucune couleur hors liste | extraction de tous les #hex, rgb(), oklch() du CSS et du HTML | 0 hors primitifs + thème |
| V2 | Aucune taille hors rampe | relevé des font-size calculés sur les 5 largeurs | 0 hors rampe |
| V3 | Aucun rayon hors liste | relevé des border-radius calculés | 0 hors des 4 autorisés |
| V4 | Contraste texte/fond | calcul WCAG sur chaque couple texte/fond détecté au rendu | ≥ 4,5:1 (< 24 px) ou ≥ 3:1 (≥ 24 px / gras) |
| V5 | Contraste des composants | bordure de champ, case, bouton, indicateur d'onglet, page courante | ≥ 3:1 (1.4.11) |
| V6 | Anneau de focus | parcours clavier complet des 5 largeurs | visible partout, ≥ 3:1, jamais supprimé |
| V7 | Débordement horizontal | mesure scrollWidth > clientWidth à 360 px | 0 occurrence |
| V8 | Champs de formulaire | chaque champ : label lié, autocomplete, message d'erreur lié | 100 % |
| V9 | Images | width/height (ou aspect-ratio) + alt réel (vide seulement si décoratif) | 100 % |
| V10 | Densité de section | part de surface portant du contenu réel | ≥ 40 % (≥ 70 % visé) |
| V11 | Tokens de composant | aucun #hex ni px de couleur en dur dans un fichier de composant | 0 |
| V12 | Champs (data-field) | toute section à fond non blanc déclare son champ | 100 % |
| V13 | Thème | les 9 contrôles T1-T9 de § 4.7 | 9/9 |
| V14 | Parité i18n | même nombre de sections par langue | écart 0 |
| V15 | axe-core | 5 largeurs × 3 langues | 0 violation |
Règle de mesure : on lance le build, puis la vérification — jamais l'inverse ; et **aucune
livraison ne s'affirme bonne sans la sortie de la commande** (règle groupe § 19).
8.3 La règle des captures
Toute modification visuelle est livrée avec des captures aux 5 largeurs (360 / 390 / 768 /
1024 / 1440) et, pour les gabarits de marque, dans les 3 langues (FR / NL / EN).
Un composant validé dans une seule langue n'est pas validé : le néerlandais est la langue la
plus longue et c'est celle qui casse les grilles.
---
9. GOUVERNANCE
9.1 Ajouter un composant — la procédure
| Étape | Ce qui est exigé | Qui décide |
|---|---|---|
| 1 | Un besoin mesuré : au moins deux pages du groupe ont ce besoin, ou une maquette validée | lead |
| 2 | Un nom en minuscules, dans la liste des composants existants (pas de synonyme : « bandeau » ≠ « bande » ≠ « ruban ») | expert design system |
| 3 | Les 8 rubriques : anatomie, variantes, tailles, 7 états, tokens, usage, a11y, anti-patterns | auteur |
| 4 | Les tokens de composant ajoutés à la couche 3 — jamais une valeur en dur | auteur |
| 5 | Les 15 contrôles V1-V15 passés, sortie de commande à l'appui | vérificateur |
| 6 | La documentation dans ce fichier (§ 5) et la ligne dans le manifest.json | auteur |
Refus automatique si : le composant duplique un composant existant · il introduit une
couleur, une taille, un rayon ou une durée hors listes · un de ses états n'est pas spécifié ·
il n'a pas d'alt/aria pour son contenu porteur de sens.
9.2 Versionner
| Version | Contenu | Règle |
|---|---|---|
| MAJEURE | suppression ou renommage d'un token, changement de mécanisme | migration obligatoire documentée, alias de compatibilité pendant 1 version |
| MINEURE | nouveau token, nouveau composant, nouveau thème | rétrocompatible, jamais de changement de valeur existante |
| CORRECTIVE | correction de contraste, de libellé, de documentation | peut changer une valeur si et seulement si elle est documentée comme correction d'accessibilité |
Règles :
- Un token ne change pas de valeur sans entrée de journal. Une valeur de charte mesurée ne
se « ravive » pas : si elle change, c'est une décision datée et signée.
- Un composant déprécié reste documenté une version entière, avec son remplacement.
- La version du design system est déclarée dans le HTML (
<meta name="tero-ds" content="2.0.0">)
pour qu'un audit puisse dire quelle version tourne en production.
- Aucun thème ne survit à sa marque (§ 4.6.4).
9.3 Portes de qualité (dans l'ordre, aucune ne se saute)
| Porte | Nom | Critère de passage |
|---|---|---|
| Q0 | Faits | toute donnée factuelle a une source · aucune invention de marque (logo, chiffre, date, prix) |
| Q1 | Tokens | V1-V3 et V11-V13 : 0 écart |
| Q2 | Accessibilité | V4-V9, V15 : 0 écart ; tous les états déclarés |
| Q3 | Structure | V10, V14, hiérarchie Hn, un seul H1, captures i18n |
| Q4 | Performance | budgets § 8.1 respectés |
| Q5 | Contenu | lexique, accents français, graphies, sources |
| Q6 | Livraison | build puis vérification, sortie de commande jointe, journal des écarts |
9.4 Rôles
| Rôle | Décide de | Ne décide pas de |
|---|---|---|
| Lead | arbitrages d'équipe, priorité, intérim des décisions client | ne tranche aucun fait de marque à la place du client |
| Brand (expert 01) | logo, déclinaisons, placement de marque, endossement | ne modifie pas la rampe, la grille ni les composants |
| Design system (expert 02) | tokens, composants, gabarits, patterns, mécanisme de thème | ne tranche pas une couleur de marque non mesurée |
| Accessibilité (expert 03) | seuils, focus, états, vérification | ne redéfinit pas la palette |
| Client | couleurs de marque, logo de référence, WildYouth, conteneur, entités | — |
9.5 Le tableau des décisions prises (journal)
| # | Décision | Statut | Conséquence si refusée |
|---|---|---|---|
| D1 | Mot-symbole tero en minuscules | [MESURE + brief] | les 4 fichiers du lead restent faux |
| D2 | 3 couches de tokens + @layer | [REGLE] | les thèmes se battent à l'ordre d'arrivée |
| D3 | 4 champs (light-a, light-b, deep, accent) | [REGLE] | le focus teal reste illisible sur fond sombre (C9) |
| D4 | Focus sable sur fond profond | [REGLE, mesuré 11,20:1] | 2,97:1 : échec 1.4.11 |
| D5 | --ui-border-strong #767676 pour les champs | [REGLE, mesuré 4,54:1] | bordures à 1,41:1 : échec 1.4.11 |
| D6 | Placeholders en #323232 | [REGLE, mesuré 12,82:1] | 1,96:1 : échec 1.4.3 |
| D7 | Rampe fluide à bornes mesurées, 9 paliers | [REGLE] | des media queries typographiques, donc 30 tailles |
| D8 | Graisse autorisée = fichier existant (400/500/600/700/900) | [MESURE] | h2 800 synthétique conservé |
| D9 | 3 variables d'accent par thème | [REGLE, mesuré] | 6 sous-marques sur 8 livrent des liens sous AA |
| D10 | Repli --ui-accent-text: null → liens en encre soulignés | [REGLE] | il faudrait inventer des teintes dérivées |
| D11 | Thème = accent + imagerie, rien d'autre | [REGLE + brief lead] | le groupe se dissout dans ses sous-marques |
| D12 | Plafond de 5 valeurs par thème | [REGLE] | la dérive à 164 valeurs recommence |
| D13 | Une seule ombre, réservée aux surfaces flottantes | [REGLE] | retour de la carte empilée |
| D14 | Autoplay de carrousel interdit | [REGLE, WCAG 2.2.2 + INP] | non-conformité |
| D15 | Neutralisation globale du mouvement en 1 bloc | [REGLE] | 25 composants à répéter, donc 3 oublis |
---
10. QUESTIONS À FAIRE TRANCHER PAR LE CLIENT
- Logo de référence : mot-symbole 5 tracés (
_audit/logo-tero-2025.svg, rapport 2,500) ou
vectoriel padel 4 tracés (logo-tero.svg, rapport 1,983) ? Les deux sont dans le dépôt (C5).
- Déclinaisons couleur du logo : noir, blanc, navy
#0D1F30, ou une par pilier ?
Cinq couleurs non déclarées circulent déjà dans les fichiers officiels (C4).
- Réédition vectorielle : le studio réédite-t-il 5 SVG aujourd'hui construits sur un masque
raster 533 × 237 (C6) ?
- Almarose Black (900) : le groupe l'utilise-t-il (fichier à récupérer) ou reste-t-il à 400→700 ?
Et confirme-t-on la suppression du h2 800 synthétique du modèle padel (C3) ?
- Sauge : Tero Studio a une sauge mesurée
#7C8F7A; tero.be porte#97AB83(620 occurrences)
qui n'est pas la même couleur. Est-ce une dérive à résorber vers la sauge studio (C15 § 4.5) ?
- Conteneur : 1 100 px (mesuré sur tero.be) ou 1 344 px (règle du modèle padel) pour tout le
groupe (C23) ?
--ui-accent-textpour Tero Studio et Garisart : le studio fournit-il une variante texte
mesurée ≥ 4,5:1, ou applique-t-on le repli « liens en encre soulignés » (§ 4.3) ?
- BPP
#A56933: 4,50:1 exactement. Durcit-on la teinte de 5 % (donc on change une couleur
de marque) ou applique-t-on le repli (C § 4.5) ?
- 4ᵉ rayon : accepte-t-on
8 pxréservé aux champs et notifications, ou les champs prennent-ils
12 px (§ 3.6) ?
- Entités à confirmer : « Tero Lodges » et « River Woods Beach Club » existent-ils (C7) ?
Et la liste complète des marques par pilier (dont immorun.lu, bouwrun.be, buildandrun.be,
12kmanderlecht.be) ?
- WildYouth : où, combien (un mot par page ?), ou retrait du chargement (question ouverte n° 4
de la charte, toujours sans réponse) ?
- Couleur d'erreur : terracotta
#B93815(5,77:1) convient-il comme couleur d'erreur
sémantique, ou le client veut-il une 8ᵉ couleur (C10) ?
- Co-marques (IMMOBEL sur Immorun, sponsors) : quel emplacement autorisé, et jusqu'où vont
les droits d'usage des logos partenaires (§ 4.6.3) ?
---
DELTA A INTEGRER DANS LA CHARTE
Corrections et ajouts à appliquer par le lead, dans l'ordre où ils débloquent le reste.
- Corriger la casse du mot-symbole dans les 4 fichiers :
tero-group-rules.md§ 4.3 L276 et
§ 5.1 L292 et § 18 L605 · DESIGN.md L137 · design-tokens.json L220 ("wordmarkCase": "TERO"
→ "minuscules"). Formulation à retenir : **mot-symbole tero (minuscules) · graphie
rédactionnelle Tero · entité Tero Group · TERO en capitales : jamais.**
- Réécrire la règle du monogramme (
tero-group-rules.md§ 4.1 L262, piège n° 6 ;DESIGN.md
L142 ; design-tokens.json L242) : [MESURE] le monogramme est le glyphe o du mot-symbole
(rapport 0,6835, identifié à ×48 du 5ᵉ tracé _audit/path-geometry.json) ; il ne remplace
jamais le mot-symbole dans un en-tête ni dans Organization.logo ; son usage comme icône
d'application exige un carré qui n'existe pas encore.
- Corriger la règle des graisses (§ 3.1 L190, § 3.3 L221,
DESIGN.mdL69-70,
design-tokens.json L86) : remplacer « Almarose s'arrête à 700 / 800-900 = faux gras » par
**« une graisse n'est autorisée que si son .woff2 existe : 400/500/600/700/900 ; le 800 est
interdit (aucun fichier) »**, et corriger h2 800 dans _ref/teropadelclub/DESIGN.md L59.
- Étiqueter correctement la règle des rayons (
DESIGN.mdL99) : `[RÈGLE, à partir de
[MESURÉ] 15 rayons distincts] ; et justifier 12 px` comme choix face au mesuré dominant
(20 px, 188 occurrences) — ou passer à 20 px. Idem pour le CTA (§ 7.1 L367) : la mesure est
38 px, la règle est 999 px.
- Supprimer les fourchettes de la rampe (
DESIGN.mdL76-85) et publier une seule rampe
scalaire : les 12 paliers de tokens.css, convertis en clamp() dont les **bornes sont les
tailles mesurées** (12→13, 13→14, 14→15, 15→16, 17→18, 20→25, 30→32, 40→50, 60→80) ; y ajouter
les 2 paliers manquants de DESIGN.md (--text-body-l, --text-title-s-l) ou les retirer
des deux fichiers.
- Corriger la couleur de texte secondaire (
tokens.cssL14, § 2.1 L129) : `--tero-muted
#B9B9B9 ne porte plus de texte ; libellés et placeholders → --ui-fg-muted #323232`
(12,82:1 sur blanc, 11,70:1 sur #F6F4F4) ; renommer le primitif en --tero-disabled.
- Corriger l'anneau de focus (§ 7.1 L372,
tokens.cssL83) : couleur dépendante du champ
— teal sur fond clair (5,43:1), sable #FAD282 sur fond profond (11,20:1 sur #212121
contre 2,97:1 aujourd'hui) — plus un halo de séparation de 2 px pour rester visible sur un
aplat teal ou terracotta.
- Ajouter les tokens d'état manquants (nouveau § 2.6) :
--ui-dangerterracotta (5,77:1),
--ui-success teal (5,43:1), --ui-info pétrole (9,50:1), --ui-warning = icône + encre
(le sable est à 1,44:1 : jamais du texte jaune).
- Séparer filet décoratif et bordure de champ (§ 8.3 L411,
tokens.cssL31-33) :
--ui-border #D9D9D9 (1,41:1, filets seuls) et --ui-border-strong #767676 (4,54:1,
tout champ, case, sélecteur — exigence 1.4.11).
- Publier l'échelle d'espacement 8 pt complète (§ 8.1 L399,
tokens.cssL69-74) :
4·8·12·16·24·32·48·64·96·128·192 ; conserver 48/72/96/144 **comme 4 profils de rythme de
section** (flush/tight/normal/loose), pas comme échelle générale.
- Ajouter les tokens de mouvement (§ 9 L423-429) : 3 courbes (
.23,1,.32,1·.77,0,.175,1·
.32,.72,0,1) et 6 durées (140/200/240/380/680 ms + 55 s pour la seule boucle linéaire),
repris à l'identique du modèle padel ; interdire tout ease écrit à la main.
- Ajouter
Almarose Fallback(tokens.cssL37) :size-adjust: 85.08%,
ascent-override: 90.5%, descent-override: 21.2%, line-gap-override: 3.3%, inséré avant
les polices système — c'est la correction du CLS mesuré (0 → 0,0334).
- Ajouter les tokens manquants de structure :
z-indexnommé, points de rupture, colonnes,
largeur de colonne de lecture, --header-h (dont dépend scroll-padding), ratios d'image,
une seule ombre flottante et la règle « jamais sur une carte de contenu ».
- Créer la couche 3 (composants) et un fichier par composant : c'est le manque structurel
(C17). Sans elle, chaque page réinvente ses valeurs — mécanisme exact des 536 feuilles.
- Créer le mécanisme de thème (§ 4) :
@layer, liste blanche de 11 variables,
data-theme sur <html>, data-field sur les sections, plafond de 5 valeurs par thème
et les 9 contrôles bloquants T1-T9.
- Documenter la table des accents de sous-marque mesurés (§ 4.5) avec les 12 accents relevés
et leurs ratios recalculés : elle remplace la question ouverte n° 2 de la charte par une
réponse chiffrée — un thème a besoin de 3 variables d'accent, pas d'une.
- Résoudre la contradiction
§ 2.3.5 ↔ § 7.3sur le teal en bande profonde : écrire les
trois lignes (accent interactif = sable 11,20:1 · aplat teal avec texte blanc autorisé ·
jamais de texte teal).
- Renommer le contrat OpenDesign (
tokens.cssL29-34,design-tokens.jsonL66-73) :
--muted doit pointer sur --ui-fg-muted (#323232) et non sur le gris à 1,96:1 ; et la même
entrée ne doit pas figurer à la fois dans aplatOnly et dans forbidden
(design-tokens.json L202 et L209).
- Ajouter les 5 dérives des fichiers logo à la liste des couleurs interdites § 2.5 :
#075F83, #068973, #F4C567, #CEA4A4, #292929 — et compléter § 4.1 qui ne liste que
6 fichiers alors que le relevé de ce document en compte 14 (12 du miroir WordPress,
2 vectoriels du projet padel).
- Créer
content/sources/tero-brands.jsoncomme source unique des marques (clé, nom
officiel, casse, pilier, domaine, accent, statut) et le faire lire par manifest.json :
aujourd'hui le manifeste invente « Tero Lodges » et « River Woods Beach Club » et omet 4
domaines (C7).
- Mettre à jour le
SKILL.mddu plugin et la skill enregistrée (regles-tero-group) :
retirer « graisse 800/900 (faux gras : la famille s'arrête à 700) », remplacer la formule du
logo, ajouter la règle « **une sous-marque surcharge un accent et une imagerie, jamais la
typographie, la grille ni les composants** », et renvoyer aux § 3 et § 4 de ce document.
- Mettre à jour
manifest.json: ajouter les fichiers des 3 couches et du dossier
themes/ dans files, déclarer craft.applies complété par accessibility-baseline, et
corriger l'inventaire scope.subBrands (delta 20).
- Publier le contrat de vérification (§ 8) dans
tero-group-rules.md§ 16 : 15 contrôles
V1-V15, les budgets (12 couleurs, 9 tailles, 4 rayons, 6 durées par page) et la règle des
captures 5 largeurs × 3 langues.
- Ajouter une section « gouvernance » à
tero-group-rules.md(§ 9 de ce document) :
procédure d'ajout d'un composant, versionnement sémantique, 6 portes de qualité, rôles, et
le journal des décisions D1-D15.
- Faire trancher les 13 questions du § 10 avant toute production de gabarit : sans elles,
chaque intégrateur prendra une décision différente — ce qui est précisément l'origine des
164 couleurs, 30 tailles et 15 rayons mesurés sur le site actuel.
---
Ce que ce document n'est pas : une nouvelle marque. Aucune couleur, aucune taille, aucun
logo n'y est inventé. Les valeurs de la couche 1 sont mesurées ; celles qui ne le sont pas sont
étiquetées
[REGLE]et réversibles ; celles qui appartiennent au client sont isolées en§ 10.Une seule chose est nouvelle ici : la structure. C'est celle qui manquait — et c'est elle
qui empêche les 164 couleurs de revenir.