Accessibilité
Focus, contrastes, décor muet, mouvement réduit, et une page lisible sans script.
Le focus
Un anneau de 2 px, vert profond sur la trame, sur tout ce qui se prend au clavier. Les champs et les questions ont le leur.
| Classe | Zone | Valeur |
|---|---|---|
| .btn, a, input, select, button :focus-visible | partout sur le site | outline 2 px --grinn, décalé de 3 px (règle de base de grinn.css) |
| .tr, .tr-hdr, .tr-pied :focus-visible | sur la trame | outline-color --grinn-deep : 5,08:1 sur blanc, là où le vert seul ferait 1,70:1 |
| .tr-form .field input, select, textarea :focus-visible | les champs | outline 2 px --grinn-deep, décalé de 2 px ; la bordure passe de --ink-3 à --ink au survol et au focus |
| .tr-demo__offre:focus-visible | une offre de la démonstration (accueil) | l’anneau vert profond de la trame, décalé de -3 px, dedans ; la case s’allume comme au survol, avec son trait |
| .tr-faq__q summary:focus-visible | une question | outline 2 px --grinn-deep, décalé de -2 px, dedans : la cellule n’a pas de marge pour un anneau extérieur |
| .tr-form__plus summary:focus-visible | le repli du formulaire | outline 2 px --grinn-deep, décalé de 2 px |
| .tr-cc__corps:focus-visible, .tr-murl__corps:focus-visible | une carte de cas, une case du mur (page Réalisations) | outline 2 px --grinn-deep, décalé de -4 px, dedans : la grille coupe ce qui dépasse ; la case pâlit aussi |
| .tr-cc__appel-btn:focus-visible | le bouton de la cellule d’appel, sur l’encre | outline-color --grinn, le vert Grinn (10,3:1 sur l’encre) |
| .tr-suivant:focus-visible::after | le cas suivant (page de cas) | l’outline du lien est retiré ; un anneau de 2 px --grinn-deep, décalé de -3 px, est tracé par-dessus tout le bloc, vitrine comprise |
| .tr-logos__pause:focus-visible | le bouton pause du bandeau | outline 2 px --grinn-deep, décalé de -4 px |
| .evitement | le lien d’évitement | « Aller au contenu », hors écran, fixé en haut à gauche au focus, encre sur blanc inversée |
Vous vendez un assistant tout fait ?
Non. On construit des agents branchés sur votre métier et sur vos logiciels, jamais un assistant standard.
Faut-il nos données pour commencer ?
Non. On commence par regarder comment vous travaillez. Le périmètre technique se décide après.
Les cibles
Les hauteurs sont calculées depuis la feuille : police × interlignage + padding + bordure. La colonne 44 px dit si la cible atteint le repère de WCAG 2.5.5 ; toutes dépassent les 24 px de WCAG 2.2 (2.5.8).
| Classe | Calcul | Hauteur | 44 px |
|---|---|---|---|
| .tr .btn | 12,5 px × 1 + 15 + 15 de padding + 2 de bordure | 44,5 px | oui |
| .tr .btn--sm | min-height 44 px (le libellé seul ferait 35,5 px) | 44 px | oui |
| .tr-cta .btn sous 480 px | 12,5 px × 1,35 + 13 + 13 + 2 | 44,9 px | oui |
| .tr-form .btn sous 600 px | idem, sur toute la largeur | 44,9 px | oui |
| .tr-hdr .btn--sm | l’en-tête est hors de .tr : ni la hauteur minimale ni le padding du petit bouton de la trame ; 11,5 px × 1 + 11 + 11 de padding (.btn--sm) + 2 ; sous 360 px, 11 px × 1 + 10 + 10 + 2 | 35,5 px ; 33 px sous 360 px | non |
| .tr-form .field input, select | police --t-body, clamp(16.5px, 0.35vw + 13px, 18.5px), posée par .tr-form ; interlignage 1,6 hérité, padding 14 px, bordure 1 px | 56,4 px et plus | oui |
| .tr-faq__q summary | padding clamp(18px, 2vw, 24px) + h3 de 17 à 19 px × 1,3 | 58 px et plus | oui |
| .tr-demo__offre | toute la case est le lien : min-height 128 px (150 px de 620 à 999 px, 132 px sous 620 px) ; dès 1000 px, un tiers de la hauteur de la scène | 128 px et plus jusqu’à 999 px | oui |
| .tr-logos__pause | 44 × 44 px | 44 px | oui |
| .tr-cc__corps, .tr-suivant | toute la carte d’un cas (page Réalisations), tout le bloc du cas suivant (page de cas) : le lien couvre la cellule | la hauteur de la carte ou du bloc | oui |
| .tr-murl__corps | une case du mur des clients : min-height 96 px | 96 px et plus | oui |
| .tr-fiche dd a::after | le lien d’offre de la fiche d’un cas couvre sa ligne : 16 + 11 × 1,3 + 8 + 15 × 1,45 + 16 | 76 px | oui |
| .tr-rail a | 11,5 px × 1,35 + 6 + 6 | 27,5 px | non |
| .tr-fil a | 11 px × 1,5, dans le texte | 16,5 px | non |
Le bouton courant atteint 44 px de justesse, à 44,5. Le bouton petit de la trame (Voir l’offre dans une carte, celui du mur) tient 44 px par une hauteur minimale : son libellé seul ferait 35,5 px. Celui de l’en-tête ne l’a pas : l’en-tête est posé hors de .tr, et la règle .tr .btn--sm ne le touche pas ; il garde 35,5 px, 33 px sous 360 px. Les entrées du rail font 27,5 px ; les maillons du fil d’Ariane sont des liens dans le texte, à 16,5 px. C’est écrit ici pour qu’on le sache, pas pour l’excuser.
Le décor est muet
Tout ce qui ne porte pas d’information est caché aux technologies d’assistance : la trame, les scènes, les annotations, les cellules allumées. Le texte dit tout.
| Élément | Composant | Marque |
|---|---|---|
| L’annotation | Puce | aria-hidden ; elle redit ce que le texte dit |
| La bande | Bande | aria-hidden sur le bloc entier, section comprise |
| La cellule allumée | CelluleAllumee | aria-hidden |
| Les cellules de la page contact, de la candidature et du pied | contact.astro, carrieres.astro, Pied | aria-hidden, pointer-events none |
| Les cellules vivantes | trame.ts (canevas .tr-vivant) | aria-hidden, pointer-events none ; posées sous l’affiche de l’accueil, dans les têtes en cellule, les cases vides du mur et la tête du point de contact ; dans la cellule de la scène de l’accueil et de l’en-tête de page aussi, mais masquées derrière une illustration v2 |
| La seconde piste du bandeau des clients | Logos | aria-hidden, images à alt vide : la liste des clients ne se lit qu’une fois ; masquée en mouvement réduit |
| L’illustration v2 | Scene, et la vignette d’une offre dans Hero | une image SVG, alt vide, decoding async ; lazy, sauf la scène d’un premier écran (eager) ; ses animations CSS se coupent d’elles-mêmes en mouvement réduit |
| Une ancienne scène en boucle (aucune en service depuis le 13 octobre) | Scene | aria-hidden, tabindex -1, muted, playsinline, sans contrôles, sans autoplay |
| La scène fixe (ancienne, si le SVG manque) | Scene | alt vide, loading lazy |
| Le canevas du dôme | Dome | aria-hidden |
| Le fond quadrillé, les annotations d’un en-tête de page | EnTetePage | aria-hidden |
| La grille de l’affiche, la cellule de la scène de l’accueil | Hero, variante « centre » | aria-hidden |
| Les traits d’attache de la démonstration | Hero (svg tr-demo__liens), trame.ts | aria-hidden, focusable false, pointer-events none |
| La vignette d’une offre, et son libellé « Voir l’offre » | Hero, variante « centre » | alt vide ; le libellé visible est aria-hidden : le lien est nommé par son aria-label, « Voir l’offre » suivi du nom de l’offre |
| La flèche ↗ | Bouton, Cas, CarteCas, CasSuivant, Contact, Pied | aria-hidden ; « (nouvel onglet) » en texte masqué quand le lien sort |
| La flèche → entre deux étapes | PointFort | aria-hidden : l’ordre est celui de la liste |
| La vitrine d’un cas | Vitrine | aria-hidden sur toute la boîte, matière et captures à alt vide : la carte ou la page dit tout ce qu’elle montre |
| Les cases vides de la grille des cas et du mur des clients | realisations/index.astro, MurLogos | aria-hidden |
| Le numéro | Engagements, Etapes, Rubrique | aria-hidden : l’ordre est déjà celui de la liste |
| Les cases vides du mur | Mur | aria-hidden |
| Le logo et le portrait en réserve | Mur | aria-hidden |
| Le logo d’un client dans une carte de l’accueil ou un en-tête de page de cas | Cas, EnTetePage | alt vide : le titre, juste à côté, porte déjà le nom ; dans une carte de la page Réalisations (CarteCas), au contraire, le logo a le nom du client pour alt : il nomme le lien |
| Le logo d’une citation en réserve | CitationCas | alt vide ; le nom du client quand la citation est signée |
| La réserve de photo | EmplacementPhoto | aria-hidden ; la photo, quand elle existe, a son alt |
| Le champ piège du formulaire | FormulaireEnvoi | conteneur aria-hidden, hors de l’écran ; le champ en tabindex -1 : jamais lu ni atteint au clavier |
| Le point final | Design.astro, Rubrique | aria-hidden |
Les titres
Un h1 par page, un h2 par section, des h3 dans les cellules. Chaque section est nommée par son titre (aria-labelledby) ou, sans titre, par un aria-label.
| Niveau | Où | Qui le pose |
|---|---|---|
| h1 | un par page | Hero (id tr-titre), EnTetePage, ou le gabarit Design : la section d’ouverture le nomme par aria-labelledby |
| h2 | une par section | Tete le pose avec son id, que Section reprend en aria-labelledby ; Logos en fait une étiquette (h2.tr-etq) quand il en a une ; le mur, la cellule IA, le point fort et le cas suivant d’une page de cas posent le leur ; la grille des cas de la page Réalisations a un h2 masqué (sr-only) ; ici, chaque Rubrique |
| h3 | dans les cellules | Carte, Cas, CarteCas, AppelCas, les rôles, Engagements, Etapes, Tarifs, les colonnes du pied, et la question d’une Faq (dans son summary) |
| sans titre | une section muette | Section prend un aria-label (la citation d’une page de cas) ; la bande n’est pas une section lisible ; le bandeau de la page Réalisations, sans étiquette, n’a pas de nom : sa section n’est pas un repère |
Sans script, sans mouvement
La page se lit sans JavaScript et sans animation : c’est une condition de départ, pas un repli.
Page.astro pose la classe .js sur l’élément html avant le rendu, par un script inline. Toutes les règles de départ (opacité 0, lignes à 0 %, carrés à 0) n’existent que sous cette classe : sans script, tout est affiché et tracé. Aucune vidéo n’est plus posée depuis le 13 octobre (la section IA de l’accueil a reçu son schéma v2) ; les illustrations v2 s’animent quand même : leur mouvement est écrit dans le SVG, et la boîte du schéma IA flotte par grinn.css, ses étapes éteintes. Les questions s’ouvrent avec details, le menu est un nav ordinaire. Le formulaire, lui, passe la main à la messagerie du visiteur, après la validation du navigateur (Formulaire, rubrique 7).
En mouvement réduit (prefers-reduced-motion: reduce), grinn.css coupe toutes les transitions et les boucles de la trame, arrête le bandeau des clients (sa seconde piste masquée, la rangée défilable à la main), masque la barre de lecture, chaque illustration v2 coupe ses propres animations (son SVG pose animation: none sur tout), la boîte du schéma IA ne flotte pas et ses étapes ne s’allument pas, et les scripts suivent : grinn.js affiche tout d’emblée, trame.ts ne lance aucune ancienne scène en boucle (il n’y en a plus en service), ne fait pas flotter la scène hero ni circuler les carrés de son anneau, trace la grille sans attendre, coupe la parallaxe et les cellules sous la souris, dessine le dôme en une seule image, et ne fait pas tourner les offres du premier écran (leurs traits restent tracés ; le survol et le focus d’une offre allument encore son trait, sans transition ni flux).
Sans script, la démonstration de l’accueil garde ses cases et leurs liens, la scène hero sans flottement, sans traits d’attache ni anneau : rien ne manque pour choisir une offre.
La page est en français (lang="fr"), sans zoom bloqué (width=device-width, initial-scale=1), et commence par le lien d’évitement vers le contenu.
Les contrastes
Les ratios sont calculés et vérifiés dans la page Couleurs, sur les paires que la trame emploie. Ils ne sont pas recalculés ici.
Toutes les paires de texte passent 4,5:1. Dans la démonstration de l’accueil, « Voir l’offre » passait au vert profond sur le vert clair au survol, à 4,497:1 : il passe à l’encre depuis le 12 octobre. La plus juste est le vert profond sur la cellule pâle, à 4,81:1. Le détail, paire par paire, avec la pastille et l’emploi : Couleurs, rubrique 04.
Ce qui reste à faire
Ce que le code ne fait pas encore, dit en clair. Rien ici n’est caché derrière une réserve.
- Le formulaire part par le relais depuis le 12 octobre (Formulaire, rubrique 6) : bilan des erreurs en tête, liens vers les champs, champ refusé marqué, focus sur le premier. Les messages d’état sont annoncés poliment (aria-live polite), pas en alerte, pour ne pas couper l’annonce du champ qui reçoit le focus : à valider avec un lecteur d’écran.
- Les logos du bandeau ont le nom du client pour alternative, ceux du mur des clients et des cartes de la page Réalisations aussi ; un client sans fichier reste en texte, en Outfit. Sur la page Réalisations, le badge « cas » se lit à la suite du nom, dans le bandeau comme dans le nom des liens du mur.
- Le bandeau des clients défile plus de cinq secondes. Le survol l’arrête, et un bouton pause (44 × 44 px, aria-pressed, libellé qui change) l’arrête au clavier et au doigt, comme le demande le critère 2.2.2 de WCAG. Sans script, le bouton reste caché et seul le survol arrête la rangée ; en mouvement réduit, elle ne défile pas.
- La photo de groupe est affichée depuis le 12 octobre (public/assets/equipe/groupe.webp), avec son alt : « L’équipe Grinn, à Nantes » sur l’accueil et la page de l’équipe, « L’équipe Grinn » sur la page carrières. C’est le seul alt de photo à relire. Les photos des réalisations (les cartes des pages d’offre), des lieux et les portraits sont en réserve : une cellule marquée aria-hidden, avec le format attendu. Les captures des cas, dans leur vitrine (les cartes de l’accueil depuis le 13 octobre, la page Réalisations, les pages de cas), sont décoratives : la carte ou la page dit ce qu’elles montrent.
- Le bouton de l’en-tête (35,5 px, 33 px sous 360 px), les entrées du rail (27,5 px) et les maillons du fil d’Ariane font moins de 44 px.
- Le tour des offres du premier écran dure 21,6 s (deux passages, six offres, 1,8 s chacune), et le trait de l’offre allumée défile tant qu’elle l’est ; il n’a pas de bouton pour s’arrêter. Le survol et le focus dans la démonstration l’arrêtent, mais il reprend quand ils en sortent ; il s’arrête seul après deux passages, et le mouvement réduit l’empêche. Le critère 2.2.2 de WCAG demande un moyen de mettre en pause ce qui bouge plus de cinq secondes : à trancher.
- Les illustrations v2 bougent sans fin, par le CSS de leur SVG : l’image flotte (5 s), le vert respire, les nœuds s’allument ; sur l’accueil, trame.ts fait en plus flotter la scène hero et circuler les carrés de son anneau, et grinn.css fait flotter la boîte du schéma IA (5 s) pendant que ses trois étapes s’allument tour à tour (6,6 s). Rien ne les arrête, sinon le mouvement réduit, qui les fige. Le critère 2.2.2 de WCAG demande un moyen de mettre en pause ce qui bouge plus de cinq secondes à côté d’autre contenu : à trancher, avec le tour des offres.
- Aucun test avec un lecteur d’écran n’a été fait. Ce que ces pages disent vient du code, pas d’un parcours réel.
RègleLa page se lit sans script, sans mouvement, sans souris et sans image ; le décor se tait, le texte dit tout.