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

ÉtiquetteSignificationCe que ça autorise
[MESURE]Valeur relevée dans un fichier du dépôt ou calculée à partir de valeurs relevéesOn 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 techniqueOn ne l'invente pas : on l'isole et on attend

Sources lues pour ce document :

SourceCe 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.jsonInventaire de marques, capacités, fichiers déclarés
_audit/logo-tero-2025.svg, _audit/path-geometry.jsonGé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 padelVariantes 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]

L605 de tero-group-rules.md (checklist), design-tokens.json L220

("wordmarkCase": "TERO"), tero-group-rules.md § 4.3 L276.

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.

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.

intégrateur qui suit la checklist écrit TERO dans le HTML du logo.

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]

DESIGN.md L142 (« la vignette anneau utilisée comme logo »), design-tokens.json L242.

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.

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.

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]

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) »).

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.

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

seule graisse réellement synthétique (le 800) par le modèle censé être la référence.

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

§ 2.2 affirme que les 7 couleurs déclarées sont « les seules couleurs de marque déclarées ».

FichierviewBoxTracésCouleurs portéesStructure
2023/12/logo-blue.svg385 × 1700 path#075F83raster masqué
2023/12/logo-green.svg385 × 1700 path#068973raster masqué
2023/12/logo_yellow.svg385 × 1700 path#F4C567raster masqué
2023/12/logo-tero-white.svg170 × 750 pathwhiteraster masqué
2023/12/logo-footer.svg144 × 630 path(masque)raster masqué
2023/12/logo-hero.svg128 × 1962#068973 + blancvectoriel
2023/12/logo-venues-hero.svg128 × 1963#CEA4A4 + blancvectoriel
2023/12/logo-activities-hero.svg128 × 1963#075F83 + blancvectoriel
2023/12/logo-contact.svg128 × 1963#F4C567 + blancvectoriel
2023/12/logo-header.svg28 × 431#292929vectoriel
2025/01/logo-header.svg28 × 431#292929vectoriel
2024/08/logo.svg52 × 231(héritée)vectoriel
_ref/…/logo-tero.svg3 421 × 1 7254#FFFFFFvectoriel
_ref/…/logo-tero-dark.svg3 421 × 1 7254#0D1F30vectoriel

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

(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]

contenu qui remplit exactement le viewBox (x 0 → 100,006).

4 tracés. Écart de 26 % sur la largeur relative : les deux fichiers ne peuvent pas être

la même composition.

lequel ne tranche rien ; toute maquette est invalidée si la référence change.

(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]

comme des SVG de référence.

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.

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.

« 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]

absents de la structure du groupe donnée par le client (3 piliers : Hospitality, Leisure,

Sport Events + marques citées).

terotrail.be (cité) — et Bistro 17/Knokke Out Zoute sont cités sans leur pilier

(« Bistro 17 », « Knokke Out »).

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.

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]

libellés de champ »).

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.

(#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).

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]

ce même anneau partout.

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.

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]

« les erreurs sont annoncées et explicites ».

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

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]

§ 6 et design-tokens.json contract.border = #D9D9D9.

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

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]

Le [MESURÉ] porte sur les 15+ (vrai : radiusMeasuredDistinct: 15), pas sur « 3 » qui est

un choix. § 7.2 confirme la règle.

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.

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]

design-tokens.json components.buttonPrimary ne porte aucune provenance.

main ». La valeur 999 px est une règle, pas la mesure.

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]

« 30-32 px », « 40-50 px », « 60-80 px »).

dont --text-body-l (16 px) et --text-title-s-l (25 px) qui n'existent pas dans

DESIGN.md.

exactement l'improvisation au pixel condamnée par § 3.2 (30 tailles distinctes) : l'intégrateur

choisit « au feeling » dans la fourchette.

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]

minimum (clamp 0.7rem = 11,2 px).

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]

puis L386 l'interdit en texte. Deux règles opposées dans la même section.

#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).

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]

--muted, --border, --accent). design-tokens.json components = 4 entrées

(buttonPrimary, buttonSecondary, focus, deepBand).

tableau, pagination, filtre, galerie, notification, fil d'Ariane, bandeau de marques,

état de chargement.

la dérive à 536 feuilles CSS.

C18 — Aucun token de mouvement, alors que § 9 impose des durées. [MESURE]

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.

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]

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

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.

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]

tous en clamp(). Jamais de palier redéfini en media query. »

c'est le mécanisme qui produit les 30 tailles distinctes mesurées sur tero.be.

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]

famille de repli à métriques corrigées.

(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).

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]

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

sombre) et, dans le projet padel, la dette documentée « un contraste peut passer en desktop

et échouer en mobile ».

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]

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

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

ajouter allowed.disabledSurfaceOnLight pour l'usage réel.

C25 — Ni forced-colors, ni prefers-contrast, ni prefers-reduced-motion au niveau des tokens.

aucun token ne les porte : chaque composant devra s'en souvenir.

@media. Ce qui est répété 25 fois sera oublié 3 fois.

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

OrdreFichierContenuCouche CSSSurchargeable par une marque
1tokens/01-primitives.cssCouleurs, rampe, polices, espacements, rayons, durées, z-index@layer tero.primitivesJamais
2tokens/02-semantique.css--ui-* (rôles) + les 4 champs + le contrat OpenDesign@layer tero.semantiqueOui, par liste blanche
3tokens/03-composants.css--btn-, --card-, --field-, --nav-, --faq-*…@layer tero.composantsNon (hérite du thème)
4themes/<cle-marque>.cssSurcharges de couche 2 uniquement@layer tero.theme—
—tokens/99-compat.cssAlias --tero-/--bg/--text- historiques@layer tero.compatNon — 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]

CouchePréfixeMotifExempleInterdit
1 primitifs--tero---tero-<famille>-<palier>--tero-teal, --tero-fs-title-m, --tero-space-32Un primitif nommé par un usage (--tero-cta)
2 sémantiques--ui---ui-<rôle>[-<variante>]--ui-fg-muted, --ui-border-strong, --ui-accentUne couleur brute
3 composants--<composant>---<composant>-<partie>-<état>--btn-bg-hover, --card-title-sizeUn composant sans token (valeur en dur)
Compatibilité--tero-* historique + --bg/--fg/…inchangé--bg, --text-bodyToute nouvelle écriture

Règles de nommage opposables :

token de composant (le calcul vit dans la CSS du composant).

--btn-bg) ; la documentation et les commentaires sont en français.

(convention Tailwind 4) ; tout le reste s'écrit en entier.

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

ChampFondUsageAttribution
light-a#FFFFFFdéfaut : contenu, éditorialdata-field="light-a" ou rien
light-b#F6F4F4section secondaire, alternancedata-field="light-b"
deep#000000 ou #212121bande profonde, hero sombre, pied de pagedata-field="deep"
accent--ui-accentbandeau de chiffres, bloc d'action, encart marquedata-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 :

TokenValeursur #FFFFFFsur #F6F4F4Usage
--ui-fg#00000021,00:119,17:1texte principal
--ui-fg-muted#32323212,82:111,70:1texte secondaire, libellés, placeholders
--ui-fg-subtle#6B6B6B5,33:14,86:1légendes, mentions, numéros d'ordre
--ui-disabled-fg#7676764,54:14,15:1état désactivé (le #B9B9B9 du site fait 1,96:1)
--tero-disabled#B9B9B91,96:11,79:1jamais 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) :

NiveauTailleGraisseInterlignageInterlettrage
Sur-titre--tero-fs-label600, capitales1,20+0.18em
H1--tero-fs-display700 (900 si almarose-900 est livré)1,02-0.025em
H2--tero-fs-title-l6001,08-0.015em
H3--tero-fs-title-m500-6001,15-0.015em
H4--tero-fs-title-s5001,25-0.01em
Chapô--tero-fs-lead4001,600
Corps--tero-fs-body4001,650
Emphasehérite600——
Mention--tero-fs-meta4001,450

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

Couleursur #FFFFFFsur #F6F4F4VerdictUsage autorisé
#00000021,00:119,17:1AAAtout
#21212116,10:114,70:1AAAtout
#004B69 pétrole9,50:18,67:1AAAtexte, liens
#B93815 terracotta5,77:15,27:1AAerreur, chiffres
#007864 teal5,43:14,95:1AAtexte, liens
#6B6B6B5,33:14,86:1AAmentions
#7676764,54:14,15:1AAbordure de champ
#32323212,82:111,70:1AAAtexte secondaire, placeholders
#B9B9B91,96:11,79:1échecjamais du texte (C8)
#D9D9D91,41:11,29:1non-textefilets décoratifs seuls (C11)
#97AB83 sauge dérive2,48:12,36:1échecinterdit

Aplats : texte à poser dessus

Aplattexte blanctexte noirDécision
#007864 teal5,43:1 AA3,87:1blanc
#004B69 pétrole9,50:1 AAA2,21:1blanc
#B93815 terracotta5,77:1 AA3,64:1blanc
#FAD282 sable1,44:114,61:1 AAAnoir
#D7B4B4 rose poudré1,90:111,08:1 AAAnoir
#00B7FF cyan2,28:19,22:1 AAAnoir
#FF1D58 rose vif3,77:1 échec5,57:1 AAnoir (le blanc échoue)
#000000 noir21,00:1—blanc

Fond profond : ce qui survit

Couleursur #000000sur #212121Usage sur bande profonde
#FFFFFF blanc21,00:116,10:1texte (toujours)
#FAD282 sable14,61:111,20:1accent interactif, focus
#D7B4B4 rose poudré11,08:18,49:1accent, sur-titre
#00B7FF cyan9,22:17,07:1accent, icône
#FF1D58 rose vif5,57:14,27:1texte sur #000 ; grand texte seulement sur #212121
#007864 teal3,87:1 (échec AA)2,97:1 (échec 3:1)jamais — ni texte, ni focus (C9)
#B93815 terracotta3,64:1 échec2,79:1 échecjamais
#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émentStatutValeur / règleCe qu'une sous-marque a le droit d'écrire
Accent (aplat de marque)SURCHARGEABLE--ui-accentune couleur mesurée sur son site
Texte sur accentSURCHARGEABLE, obligatoire--ui-accent-on = noir ou blancobligatoire dès qu'un accent est déclaré
Accent en texte sur fond clairSURCHARGEABLE, contrôlé--ui-accent-text ≥ 4,5:1sinon null → les liens restent en encre
Survol d'accentSURCHARGEABLE--ui-accent-hover ≥ 3:1 avec l'accentune variante plus dense
Aplat doux d'encartSURCHARGEABLE--ui-accent-softune teinte très claire de l'accent
Bande profondeSURCHARGEABLE--ui-deep, --ui-deep-fgune couleur de fond + l'encre qui tient 4,5:1
Anneau de focusSURCHARGEABLE (couleur) / VERROUILLÉ (géométrie)--ui-focus ≥ 3:1 sur tous les champs de la marque ; 3 px / offset 3 pxune couleur, jamais une épaisseur
Traitement d'imageSURCHARGEABLE--ui-image-treatmentnone ou un filtre (duotone, désaturation)
Décoration / motifSURCHARGEABLE--ui-decorationun motif de points, une trame — jamais un logo redessiné
Famille typographiqueVERROUILLÉAlmarose + WildYouthrien. Aucune 3ᵉ famille, aucune serif
GraissesVERROUILLÉ400/500/600/700/900 (fichiers existants)rien
Rampe typographiqueVERROUILLÉ9 paliers fluides, § 3.4rien
Interlignage / interlettrageVERROUILLɧ 3.4rien
Mesures de lectureVERROUILLÉ58/62/66/42 chrien
Échelle d'espacementVERROUILLÉ8 pt complète, § 3.1rien
Rythme de sectionVERROUILLÉ4 profils --tero-rhythm-*choisir un profil, pas une valeur
Grille et conteneurVERROUILLÉ12 colonnes, 1 100 px (cf. C23)rien
Points de ruptureVERROUILLÉ360/390/768/1024/1440rien
RayonsVERROUILLÉ12/20/999 + 8 (champs)rien
FiletsVERROUILLÉ1 pxrien
Durées et courbesVERROUILLÉ3 courbes, 6 durées, § 3.1rien
ÉtatsVERROUILLÉ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 tactilesVERROUILLÉ44 pxrien
Empilement (z-index)VERROUILLÉéchelle nomméerien
OmbresVERROUILLÉune seule ombre flottanterien
Tokens de composantVERROUILLɧ 3.6rien
Structure des gabaritsVERROUILLɧ 6le contenu, pas l'ordre
Endossement TeroVERROUILLÉmention + lien tero.berien : jamais retiré
LogoVERROUILLÉ (fichiers)fichiers officiels tels quelschoisir 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-text n'est pas fourni et mesuré ≥ 4,5:1, l'accent n'est

jamais du texte.** Les liens du corps de texte passent en --ui-fg (noir) et restent

souligné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ègleDétailVérification automatique
data-theme sur <html>une seule valeur, jamais sur un sous-arbre1 occurrence par page
data-field sur chaque sectionobligatoire si le fond n'est pas light-atoute section avec un fond non blanc sans data-field = erreur de build
thème absentdata-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 groupeinterditla 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).

MarqueAccentsur blanc (texte)blanc sur aplatnoir sur aplatsur #212121Verdict --ui-accent-text--ui-accent-on
Tero Group#0078645,43:15,43:13,87:12,97:1 échecaccent lui-même ✅#FFFFFF
Tero Padel Club#FF2E823,52:13,52:1 échec5,97:14,57:1#D9216B fourni ✅#000000
Tero Padel Club#E9F44E (lime)1,20:11,20:117,52:113,43:1non (aplat)#000000
Tero Studio#7C8F7A (sauge)3,46:13,46:16,07:14,65:1manquant ❌#000000
Tero Trail#2B4336 (forêt)10,72:110,72:11,96:11,50:1accent lui-même ✅#FFFFFF
Tero Trail#8FB8A5 (forêt clair)2,19:12,19:19,57:17,34:1non (aplat / accent sur fond profond)#000000
Immorun#263062 (navy)12,49:112,49:11,68:11,29:1accent lui-même ✅#FFFFFF
Immorun#E0BB8E (sable)1,80:11,80:111,67:18,95:1non (aplat)#000000
Batirun#C0001E (rouge)6,44:16,44:13,26:12,50:1accent lui-même ✅#FFFFFF
Batirun#0E143C (encre)17,74:117,74:11,18:11,10:1accent lui-même ✅#FFFFFF
Bruxelles Premier Padel#A56933 (orange)4,50:1 ⚠️4,50:14,67:13,58:1limite exacte ⚠️#000000
Garisart Sports Club#AAC440 (lime)1,97:11,97:110,68:18,19:1manquant ❌#000000
tero.be — sauge dérive#97AB832,48:12,48:18,47:16,49:1interdit—

Quatre enseignements mesurés :

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.

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.

(#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].

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 :

CasMécanismeExemple
Marque permanente du groupedata-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èmeco-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] :

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.

interdits. Si le client veut un endossement graphique, il est dessiné par le studio.

dans le même bloc que le logo Tero, et le sponsor n'apparaît jamais comme éditeur du site.

qui survit à son événement est de la dette (c'est le mécanisme qui a produit les 536 feuilles).

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ôleSeuilSinon
T1Le fichier ne contient que des variables de la liste blanche § 3.50 variable hors listebuild en échec
T2Aucune occurrence de --tero- dans un fichier de thème0build en échec
T3Aucun sélecteur autre que [data-theme='…'] et [data-theme-scope='event']0build en échec
T4--ui-accent-on déclaré si --ui-accent l'estobligatoirebuild en échec
T5contrast(--ui-accent, --ui-accent-on) ≥ 4,5calculébuild en échec
T6contrast(--ui-accent-text, #FFFFFF) ≥ 4,5 ou --ui-accent-text: nullcalculébuild en échec
T7contrast(--ui-focus, --ui-surface) ≥ 3 sur les 4 champs du thèmecalculébuild en échec
T8Nombre de couleurs déclarées dans le thème ≤ 5≤ 5avertissement bloquant
T9Le thème est enregistré dans content/sources/tero-brands.json avec son pilierprésentbuild 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.

ÉtatDéclencheurTraduction visuelle autoriséeToken d'étatExigence d'accessibilité
repos—état de référence--bg, --fgcontraste ≥ 4,5:1 (texte) / 3:1 (composant)
survol (hover)souris uniquementchangement de fond, de couleur, soulignement--*bg-hovertoujours gardé par @media (hover:hover) and (pointer:fine) ; jamais porteur d'une information exclusive
focus visibleclavieranneau 3 px + offset 3 px + halo--ui-focus≥ 3:1 sur tous les champs ; jamais outline:none ; ≥ 1 px d'épaisseur (2.4.13)
actifappui maintenutransform: scale(0.97), jamais un déplacement--btn-1-active-scaleaucune information portée par l'échelle
désactivénon applicableencre --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é
videaucune donnéephrase + porte de sortie (CTA) — jamais « Aucun résultat. »--ui-fg-mutedle message est dans le flux, lu par le lecteur d'écran (role="status")
erreursaisie invalidebordure --ui-danger 2 px + message lié--field-*-invalidaria-invalid, aria-describedby, cause + correction
chargementattentesquelette aux dimensions finales, ou libellé au participe dans le bouton--ui-fg-mutedaria-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 :

Un #hex dans un fichier de composant = échec du contrôle § 8.2.

simultanés sur une carte de contenu. La séparation vient du filet ou de l'espace.

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

Un alt vide uniquement pour une image décorative ; un visuel porteur de sens a un alt réel.

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
Variantesbtn--primary (aplat) · btn--secondary (contour 1 px) · btn--tertiary (lien-bouton souligné, sans fond) · btn--danger (actions destructives, --ui-danger)
Taillesmd (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-bgfgbg-hover, --btn-2-, --btn-3-, --btn-radius (999), --btn-min-h, --btn-transition (--tero-dur-fast`)
États spécifiqueschargement : libellé au participe (« Envoi… » → « Envoyé »), spinner dans le bouton, aria-busy="true", largeur stable (le texte change sans reflow)
Usageun 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
A11ycible ≥ 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-patterndeux 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 »)
Varianteslink--inline (dans le corps) · link--standalone (en dehors du corps, peut perdre le soulignement) · link--back · link--external
Tailleshérite de --tero-fs-body / --tero-fs-meta ; jamais sous --tero-fs-meta
Tokens--link-fg, --link-underline-offset, --link-underline-thickness-hover
Étatssurvol : 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)
Usagele 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
A11y1.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: nullle 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)
Variantescard--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)
Taillescompact (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
Étatssurvol : 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
Usagela 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
A11yun 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 »)
Variantesgrid--cards (auto-fit) · grid--places (2 colonnes asymétriques 7/5) · grid--logos (bandeau de marques) · grid--stats (bande de chiffres)
Tailles1 / 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-
Étatsvide : bloc « Aucun lieu dans cette région » + lien vers l'index complet
Usagealterner 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
A11yliste 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
Varianteshero--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
Étatschargement : poster affiché immédiatement (jamais de dégradé gris) · réduction de mouvement : vidéo jamais chargée, poster seul
Usageun 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)
A11ycontraste 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
Variantesband--statement (une phrase, un accent) · band--stats (chiffres) · band--cta (action unique) · band--footer
Taillespadding-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)
Usagefond : #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
A11yle changement de champ est le contrat de contraste : data-field="deep" obligatoire, vérifié au build

C-07 · Bande de chiffres · stats

Anatomieliste de paires (grand nombre + libellé court), séparées par un filet vertical
Variantesstats--band (dans une bande profonde) · stats--inline (dans un paragraphe, style inline) · stats--kpi (avec un accent par chiffre)
Taillesnombre --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
Étatsvide : jamais une bande de chiffres vide → le bloc disparaît (pas de « 0 »)
Usageun 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)
A11yla 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>)
Variantesquote--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—
Usageun 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
Variantesfaq--list (page FAQ, toutes ouvertes possibles) · faq--inline (3 questions en bas de page) · faq--single (une seule ouverte : name sur les details)
Taillesquestion --faq-question-size (20-25 px) · réponse --tero-fs-body · --faq-pad-block: 24px
Tokens--faq-* · --faq-transition (grid-template-rows, 200 ms)
Étatsouvert/fermé : marqueur +/− ou chevron rotate 180deg sur --tero-dur-base ; chargement jamais (contenu statique)
Usagela 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

Anatomierole="tablist" → role="tab" (aria-selected, aria-controls) → role="tabpanel" (tabindex 0)
Variantestabs--underline (défaut : indicateur 2 px) · tabs--pills (sectionnement secondaire, rayon pilule) · tabs--filters (voir C-21)
Taillescible ≥ 44 px de hauteur · --tabs-pad-inline: 16px
Tokens--tabs-*
Étatssélectionné : --tabs-indicator + --tabs-fg-selected (couleur + indicateur, jamais la couleur seule) · focus : anneau
Usagemaximum 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
A11ymotif 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")
Variantesbreadcrumb--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—
Usageobligatoire 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
A11yaria-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-*)
Variantesheader--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
Étatsscandé/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
Glassmorphismautorisé uniquement ici, avec --header-fallback-bg opaque obligatoire en repli (@supports not (backdrop-filter))
Usagela 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
A11yaria-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

Anatomiedéclencheur (icône + aria-label="Ouvrir le menu") → voile --overlay-bg → panneau --drawer-width → fermeture → liste de navigation → actions (réservation, langue, contact)
Variantesdrawer--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
Étatsouvert : 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
Usagele 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
A11ypiè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
Variantesfooter--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—
Usageles 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
Variantesbrandbar--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
Étatsvide : 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
Usagec'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
A11ychaque 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

Anatomiepiste (scroll-snap) → cartes (--carousel-gap) → boutons précédent/suivant (--carousel-btn-*) → pastilles de position → masque de débordement avec --carousel-peek
Variantescarousel--scroll (défilement natif + snap, le défaut) · carousel--controls (boutons) · carousel--gallery (voir C-23)
Tailles1 à 3 cartes visibles · jamais plus de 3 · --carousel-btn-size: 44px
Tokens--carousel- · --tero-z- si chevauchement
Étatsbouton 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
Usageun 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
A11yrole="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)
Variantestexte · courriel · téléphone (type="tel" + inputmode) · nombre · zone de texte · liste déroulante native (<select>) · choix multiple · date (<input type="date">)
Tailleshauteur ≥ 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)
Étatsfocus : 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
Usagele 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
A11ylabel 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
Variantescountry-select--native (défaut : natif, accessible, clavier gratuit) · country-select--radio (2 à 5 pays seulement)
Tailleshauteur ≥ 44 px · libellé « Belgique (BE) », « Luxembourg (LU) », « Pays-Bas (NL) », « France (FR) »
Tokens--field-*
Étatsinvalide : 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)
Usagele 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
Variantesconsent--required (envoi bloqué sans coche) · consent--optional (marketing, décochée par défaut) · consent--double (deux cases distinctes : exécution et prospection)
Taillescarré --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)
Étatscoché : coche vectorielle + couleur (jamais la couleur seule) · erreur : message lié + aria-invalid · désactivé : --ui-disabled-fg
Usagejamais 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 »
A11yle <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

AnatomieErreur 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
Variantestoast--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
Étatsentrée/sortie : opacity + translateY ≤ 8 px, --tero-dur-base · réduction de mouvement : apparition sans translation
Usagele 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)
A11yrole="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
Variantestable--simple (filets horizontaux, sans fond) · table--compare (lots, tarifs) · table--responsive (devient une liste de paires en HTML, pas par CSS)
Taillescellule --table-cell-pad-* · en-tête --table-head-size (13-14 px) · jamais de texte sous 12 px
Tokens--table-*
Étatsvide : ligne de message + CTA · chargement : squelette de lignes
Usageles 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

Anatomiegrille de vignettes (--gallery-gap, ratio imposé) → visionneuse (lightbox) → légende → commandes (précédent, suivant, fermer, compteur)
Variantesgallery--grid (mosaïque statique) · gallery--carousel (C-16) · gallery--lightbox (ouverture plein écran)
Taillesvignettes srcset 480/768/1024/1440 · images ≤ 90 Ko pour une carte, ≤ 200 Ko pour un hero
Tokens--gallery-* · --overlay-bg · --tero-z-modal
Étatschargement : width/height + fond --ui-surface-alt (aucun décalage) · image absente : le cadre garde son ratio, le alt reste lisible
Usagephotos 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)
A11ylightbox = 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

Anatomiegroupe de <fieldset> + <legend> → cases à cocher ou puces (--filter-chip-*) → compteur de résultats (role="status") → bouton « Effacer les filtres »
Variantesfilters--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)
Taillespuce ≥ 36 px de haut, cible 44 px · regroupement par axe (lieu, activité, public, prix)
Tokens--filter-*
Étatsactif : 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… »
Usagele 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 »
Variantespagination--numbered (pages SEO-indexables) · pagination--loadmore (chargement progressif avec URL de repli) · pagination--infinite (interdit — le pied de page devient inatteignable)
Taillescible ≥ 44 px · --pagination-size: 44px
Tokens--pagination-*
Étatspage courante : aria-current="page" + --pagination-border-current (jamais la couleur seule) · premier/dernier : Précédent/Suivant désactivés et annoncés
Usagechaque 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

Anatomieconteneur → sélecteur de lieu/date → action principale → mention de confiance (« Annulation gratuite 24 h avant » — si sourcé)
Variantesbooking--inline (dans une fiche) · booking--bar (barre collante mobile) · booking--external (sortie vers la plateforme du groupe)
Taillesbarre 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)
Étatschargement : aria-busy sur le conteneur + libellé « Vérification… » (jamais un spinner seul) · indisponible : message + alternative (téléphone du lieu, source)
Usagele 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
A11yla 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)

ComposantRôleTokensPoint d'accessibilité critique
badge / chipétiquette d'état, d'édition, de catégorie--ui-accent-soft, --ui-fgcontraste ≥ 4,5:1 ; jamais une couleur seule pour un statut
priceprix et unité--stat-*, tabular-numsl'unité est dans le HTML, jamais dans une image ; source obligatoire
testimonialavis client--quote-*verbatim + nom + société ; 3 à 6 avis maximum
partner-walllogos de partenaires--brandbar-*chaque logo a un alt = nom du partenaire ; « Ils nous font confiance » interdit
mapcarte des lieux--ui-border, --radius-mediaéquivalent textuel obligatoire (liste des lieux avec adresses)
sharepartage--btn-3-*pas de script tiers bloquant ; les URL de partage n'ouvrent pas d'onglet sans l'annoncer
newsletterinscription--field-*consentement explicite (C-19) ; pas de double pop-up
modaldialogue--tero-shadow-float, --tero-z-modalrole="dialog", aria-modal, piège de focus, Échap, focus rendu
tooltipinfobulle--toast-*jamais porteur d'une information exclusive (1.4.13) ; survol et focus
skip-linklien d'évitement--ui-focuspremier arrêt de tabulation, visible à la prise de focus
lang-switchsélecteur de langue--link-*noms de langues en clair, hreflang, jamais d'emoji-drapeau
videolecteur--radius-mediasous-titres si parole ; prefers-reduced-motion → poster seul
timelinehistoire du groupe (2020, 2022…)--stat-*, --ui-borderchaque date est sourcée ; liste ordonnée <ol>
cookie-bannerconsentement--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écificationValeur
Répond à la question « qui est Tero ? »dans les 2 premiers écrans (règle groupe § 6.3.4)
JSON-LDOrganization Tero Group avec subOrganization (les 3 piliers) · logo = mot-symbole, jamais le monogramme · sameAs = réseaux officiels uniquement (jamais une URL de prévisualisation)
Interditun 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écificationValeur
H1« Tero Hospitality », etc. — orthographe exacte, jamais abrégée, jamais traduite
Rattachementfil d'Ariane Tero > Pilier + lien vers tero.be
JSON-LDWebPage + isPartOf Tero Group · about = le pilier
Interditune 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écificationValeur
Thèmedata-theme="<marque>" — seuls accent, accent-on, accent-text, deep, focus changent (§ 4.2)
Rattachementmention + lien tero.be obligatoires (règle de la maison mère § 6.3.2)
JSON-LDOrganization (marque) avec parentOrganization = Tero Group · logo de la marque
Interditune 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écificationValeur
JSON-LDPlace (ou Restaurant si applicable) + address PostalAddress + geo si source
Données sensiblesadresse, horaires, capacité, téléphone : jamais corrigés sans source
Piègeun aggregateRating sans avis vérifiables · une photo attribuée au mauvais lieu
A11yla 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écificationValeur
JSON-LDService ou Product selon la nature · offers uniquement avec prix et devise sourcés
Usagel'unité de prix est toujours affichée (« / heure », « / pers. ») · la durée est un format normalisé
Piègeun 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écificationValeur
JSON-LDRestaurant + servesCuisine + openingHoursSpecification (source obligatoire) + menu
InterditaggregateRating, un avis inventé, une photo de stock de plat
Piègeun 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écificationValeur
Thèmedata-theme="<event>" + data-theme-scope="event" (§ 4.6)
JSON-LDEvent + organizer = Tero (ou la marque) + location + offers sourcés · jamais Article
Après l'éditionle thème événementiel est retiré de la CSS ; la page devient un compte rendu avec les résultats
Piègeun 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écificationValeur
JSON-LDArticle/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ègeun 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écificationValeur
JSON-LDFAQPage uniquement si toutes les questions sont visibles et réelles — jamais de FAQ inventée pour le référencement
Usageréponse ≤ 62 ch de mesure · questions en 500 · ordre = fréquence réelle
Piègeune 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écificationValeur
Aucune donnée de contact inventéedeux adresses, deux téléphones, un seul courriel par usage (source obligatoire)
JSON-LDContactPage + Organization (adresses)
A11yles 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écificationValeur
JSON-LDAboutPage + Organization (foundingDate sourcé — jamais deviné) · founder = personnes réelles
Interditune 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écificationValeur
Règleun 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écificationValeur
JSON-LDJobPosting par offre avec datePosted, employmentType, jobLocation — retiré à la clôture
A11ychaque offre est un lien avec un titre complet (« Coach padel — Waterloo — CDI »)
Piègedes 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écificationValeur
Une seule actionle même libellé du début à la fin ; deux CTA concurrents = échec
JSON-LDWebPage (pas Article) · Organization en about
Piègeune 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écificationValeur
Réponse HTTP404 réel (jamais 200 avec un contenu d'erreur : c'est un piège SEO mesuré)
Usageles anciennes URL redirigées en 301 arrivent ici uniquement si la cible n'existe pas
A11ypas 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écificationValeur
URL?q= partageable, indexable en noindex sur les pages de résultats
A11yrole="status" sur le compteur · le champ garde focus après filtrage · l'état vide propose une action
Piègeun surlignage par couleur de fond sans contraste calculé ; une recherche vide qui affiche tous les contenus

---

7. PATTERNS TRANSVERSES

7.1 Grille

ÉlémentRègle
Colonnes12 · gouttière --tero-gutter (16→24 px) · conteneur 1 100 px (cf. C23)
Ratios autorisés12/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étrieau moins 2 ratios de colonnes différents par page
Débordement1 à 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éralejamais vide : phrase de cadrage + porte de sortie utile
Alignementmajoritairement à gauche · 1 seul bloc centré par page
Points de rupture360 / 390 / 768 / 1024 / 1440 · zéro débordement horizontal à 360 px

7.2 Rythme et densité

ÉlémentRègleValeur
Profils de sectionexclure, resserré, normal, ample--tero-rhythm-flush/tight/normal/loose (32→44 / 44→64 / 60→96 / 72→120 px)
Alternancejamais deux profils identiques adjacents ; un profil « ample » au maximum tous les 4 blocsrègle
Entre entrées éditoriales2,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
Ancresscroll-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

NiveauTailleGraisseRôle
Sur-titrelabel600 capitales, +0.18emcontexte (« GROUPE TERO », « LIEU »)
H1display700un seul par page, dans le hero
H2title-l600section
H3title-m / title-s500-600entité, sous-section
H4title-s500sous-partie d'une carte ou d'un encart
Chapôlead4002 phrases autonomes, ≤ 58 ch
Corpsbody400≤ 66 ch
Mentionmeta400date, source, légende
Chiffrestat700, tabular-numsjamais 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

Jamais huit en-têtes centrés d'affilée.

7.5 Responsive

SeuilRègle
360 pxaucune information retirée (on réorganise, on ne supprime pas) · aucun débordement horizontal · tableaux en pile
390 pxcapture de contrôle obligatoire
768 pxpassage 2 colonnes · méga-menu → menu mobile
1024 pxgrille complète, méga-menu desktop
1440 pxconteneur centré, contenu borné (jamais des lignes de 140 caractères)
Pointeurtout :hover est gardé par @media (hover:hover) and (pointer:fine) + équivalent :focus-visible de même intensité
Saisiefont-size: max(1rem, 1em) · jamais user-scalable=no
Encochesviewport-fit=cover + env(safe-area-inset-*)
Hauteur100svh/100dvh, jamais 100vh
Imagessrcset 480/768/1024/1440/1920 + sizes explicite + width/height
Vidéodeux sources distinctes (portrait 9:16 mobile, paysage 16:9 desktop), chacune avec poster

7.6 Mouvement

UsageDuréeCourbe
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
Sortie60 à 70 % de la durée d'entréecourbe 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

TypeRègle
FormatAVIF puis WebP, repli JPEG/PNG · jamais un PNG de photo
Poidshero 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
Attributswidth + height (ou aspect-ratio) · alt réel · image LCP : eager + fetchpriority="high" + preload
Contenuphotos 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éo6-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)

le même nombre de sections. Un bloc présent en FR et absent en NL est un défaut.

EN = anglais britannique.

Bistro Pilko, Immorun, Batirun.

NL sont 15 à 30 % plus longues. Les gabarits sont testés dans les trois langues (§ 8.3).

7.9 États globaux de la page

ÉtatRègle
Chargement initialle contenu critique est dans le HTML (pas de coquille rendue en JS) · pas de loader plein écran
Erreur réseaumessage + action « Réessayer » · jamais une page blanche
JavaScript absentnavigation, FAQ, onglets et filtres restent utilisables (dégradation obligatoire)
Hors lignel'information factuelle (adresse, téléphone) reste consultable si mise en cache
prefers-contrast: morefilets → --ui-border-strong, encres secondaires → --ui-fg
forced-colors: activeoutline-color: Highlight · les aplats de marque sont remplacés par les couleurs système : l'information ne doit jamais dépendre d'un aplat
Impressioncontenu sur blanc, liens avec URL développée, navigation masquée

---

8. CONTRATS DE VÉRIFICATION

8.1 Budgets opposables

RessourceBudgetMesure de référence
CSS du socle (couches 1 à 3)≤ 45 Ko gzipl'existant : 536 feuilles, 9,1 Mo
CSS d'un thème de marque≤ 3 Ko gzip—
Nombre de fichiers CSS servis≤ 4536
Polices≤ 5 fichiers .woff2 (400/500/600/700/900) · ≤ 2 préchargés5 fichiers référencés
JS livré≤ 50 Ko gzip, aucune bibliothèque d'animationré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≤ 415+
Durées distinctes par page≤ 6 (les tokens)aucune échelle
LCP / INP / CLS< 1,5 s / < 200 ms / < 0,1cible groupe § 11

8.2 Contrôles automatiques (bloquants en intégration continue)

#ContrôleMéthodeSeuil
V1Aucune couleur hors listeextraction de tous les #hex, rgb(), oklch() du CSS et du HTML0 hors primitifs + thème
V2Aucune taille hors ramperelevé des font-size calculés sur les 5 largeurs0 hors rampe
V3Aucun rayon hors listerelevé des border-radius calculés0 hors des 4 autorisés
V4Contraste texte/fondcalcul WCAG sur chaque couple texte/fond détecté au rendu≥ 4,5:1 (< 24 px) ou ≥ 3:1 (≥ 24 px / gras)
V5Contraste des composantsbordure de champ, case, bouton, indicateur d'onglet, page courante≥ 3:1 (1.4.11)
V6Anneau de focusparcours clavier complet des 5 largeursvisible partout, ≥ 3:1, jamais supprimé
V7Débordement horizontalmesure scrollWidth > clientWidth à 360 px0 occurrence
V8Champs de formulairechaque champ : label lié, autocomplete, message d'erreur lié100 %
V9Imageswidth/height (ou aspect-ratio) + alt réel (vide seulement si décoratif)100 %
V10Densité de sectionpart de surface portant du contenu réel≥ 40 % (≥ 70 % visé)
V11Tokens de composantaucun #hex ni px de couleur en dur dans un fichier de composant0
V12Champs (data-field)toute section à fond non blanc déclare son champ100 %
V13Thèmeles 9 contrôles T1-T9 de § 4.79/9
V14Parité i18nmême nombre de sections par langueécart 0
V15axe-core5 largeurs × 3 langues0 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

ÉtapeCe qui est exigéQui décide
1Un besoin mesuré : au moins deux pages du groupe ont ce besoin, ou une maquette validéelead
2Un nom en minuscules, dans la liste des composants existants (pas de synonyme : « bandeau » ≠ « bande » ≠ « ruban »)expert design system
3Les 8 rubriques : anatomie, variantes, tailles, 7 états, tokens, usage, a11y, anti-patternsauteur
4Les tokens de composant ajoutés à la couche 3 — jamais une valeur en durauteur
5Les 15 contrôles V1-V15 passés, sortie de commande à l'appuivérificateur
6La documentation dans ce fichier (§ 5) et la ligne dans le manifest.jsonauteur

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

VersionContenuRègle
MAJEUREsuppression ou renommage d'un token, changement de mécanismemigration obligatoire documentée, alias de compatibilité pendant 1 version
MINEUREnouveau token, nouveau composant, nouveau thèmerétrocompatible, jamais de changement de valeur existante
CORRECTIVEcorrection de contraste, de libellé, de documentationpeut changer une valeur si et seulement si elle est documentée comme correction d'accessibilité

Règles :

se « ravive » pas : si elle change, c'est une décision datée et signée.

pour qu'un audit puisse dire quelle version tourne en production.

9.3 Portes de qualité (dans l'ordre, aucune ne se saute)

PorteNomCritère de passage
Q0Faitstoute donnée factuelle a une source · aucune invention de marque (logo, chiffre, date, prix)
Q1TokensV1-V3 et V11-V13 : 0 écart
Q2AccessibilitéV4-V9, V15 : 0 écart ; tous les états déclarés
Q3StructureV10, V14, hiérarchie Hn, un seul H1, captures i18n
Q4Performancebudgets § 8.1 respectés
Q5Contenulexique, accents français, graphies, sources
Q6Livraisonbuild puis vérification, sortie de commande jointe, journal des écarts

9.4 Rôles

RôleDécide deNe décide pas de
Leadarbitrages d'équipe, priorité, intérim des décisions clientne tranche aucun fait de marque à la place du client
Brand (expert 01)logo, déclinaisons, placement de marque, endossementne modifie pas la rampe, la grille ni les composants
Design system (expert 02)tokens, composants, gabarits, patterns, mécanisme de thèmene tranche pas une couleur de marque non mesurée
Accessibilité (expert 03)seuils, focus, états, vérificationne redéfinit pas la palette
Clientcouleurs de marque, logo de référence, WildYouth, conteneur, entités—

9.5 Le tableau des décisions prises (journal)

#DécisionStatutConséquence si refusée
D1Mot-symbole tero en minuscules[MESURE + brief]les 4 fichiers du lead restent faux
D23 couches de tokens + @layer[REGLE]les thèmes se battent à l'ordre d'arrivée
D34 champs (light-a, light-b, deep, accent)[REGLE]le focus teal reste illisible sur fond sombre (C9)
D4Focus 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
D6Placeholders en #323232[REGLE, mesuré 12,82:1]1,96:1 : échec 1.4.3
D7Rampe fluide à bornes mesurées, 9 paliers[REGLE]des media queries typographiques, donc 30 tailles
D8Graisse autorisée = fichier existant (400/500/600/700/900)[MESURE]h2 800 synthétique conservé
D93 variables d'accent par thème[REGLE, mesuré]6 sous-marques sur 8 livrent des liens sous AA
D10Repli --ui-accent-text: null → liens en encre soulignés[REGLE]il faudrait inventer des teintes dérivées
D11Thème = accent + imagerie, rien d'autre[REGLE + brief lead]le groupe se dissout dans ses sous-marques
D12Plafond de 5 valeurs par thème[REGLE]la dérive à 164 valeurs recommence
D13Une seule ombre, réservée aux surfaces flottantes[REGLE]retour de la carte empilée
D14Autoplay de carrousel interdit[REGLE, WCAG 2.2.2 + INP]non-conformité
D15Neutralisation globale du mouvement en 1 bloc[REGLE]25 composants à répéter, donc 3 oublis

---

10. QUESTIONS À FAIRE TRANCHER PAR LE CLIENT

vectoriel padel 4 tracés (logo-tero.svg, rapport 1,983) ? Les deux sont dans le dépôt (C5).

Cinq couleurs non déclarées circulent déjà dans les fichiers officiels (C4).

raster 533 × 237 (C6) ?

Et confirme-t-on la suppression du h2 800 synthétique du modèle padel (C3) ?

qui n'est pas la même couleur. Est-ce une dérive à résorber vers la sauge studio (C15 § 4.5) ?

groupe (C23) ?

mesurée ≥ 4,5:1, ou applique-t-on le repli « liens en encre soulignés » (§ 4.3) ?

de marque) ou applique-t-on le repli (C § 4.5) ?

12 px (§ 3.6) ?

Et la liste complète des marques par pilier (dont immorun.lu, bouwrun.be, buildandrun.be,

12kmanderlecht.be) ?

de la charte, toujours sans réponse) ?

sémantique, ou le client veut-il une 8ᵉ couleur (C10) ?

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.

§ 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.**

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.

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.

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

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.

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

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

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

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

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.

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

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

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

(C17). Sans elle, chaque page réinvente ses valeurs — mécanisme exact des 536 feuilles.

data-theme sur <html>, data-field sur les sections, plafond de 5 valeurs par thème

et les 9 contrôles bloquants T1-T9.

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.

trois lignes (accent interactif = sable 11,20:1 · aplat teal avec texte blanc autorisé ·

jamais de texte teal).

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

#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).

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

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.

themes/ dans files, déclarer craft.applies complété par accessibility-baseline, et

corriger l'inventaire scope.subBrands (delta 20).

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.

procédure d'ajout d'un composant, versionnement sémantique, 6 portes de qualité, rôles, et

le journal des décisions D1-D15.

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.