Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AGNSEFZdoKvbdsGyfSnu5S
18 KiB
Design system nav-carte (AEP)
Ce fichier existe pour que B6 à B9 n'aient pas à relire les récaps de B3 (1 PROJETS/DEV/AEP/Cascade-Refonte/recaps/B3-*.md dans le vault Dropbox) pour comprendre ce qui a été posé. Si tu es sur B6+ et que tu touches au visuel de nav-carte, lis ceci d'abord.
D'où viennent les tokens
Les couleurs, espacements, rayons, ombres, etc. ne sont jamais des valeurs en dur dans les composants nav-carte. Ils viennent de deux fichiers, chargés dans cet ordre dans nuxt.config.ts :
@transformer/ui/tokens.css— package npm partagé (git+https://git.trans-former.fr/jules/design-system.git), le schéma canonique de tokens par rôle (--bg,--text,--primary,--focus-ring,--space-*,--radius-*,--fs-*, etc.). Jamais cloné en local, jamais modifié depuis ce repo.@transformer/ui/theme/aep.css— la palette AEP spécifique (« Sobre institutionnel », validée par Jules), qui surcharge les rôles du point 1 avec les vraies couleurs : bleu nuit#1a2238, accent safran#f5b342, fond crème#f8f6f1. C'est ce fichier qui rend--primary,--accent,--bgetc. concrets pour AEP.
assets/css/main.css définit ensuite des variables --nav-* (héritées de l'ancienne palette V1, avant l'arrivée du DS). Elles sont alias des rôles ci-dessus : --nav-bg: var(--bg), --nav-text: var(--text), etc. Elles existent uniquement parce que tout le code existant (avant B3) les consomme — ne pas les supprimer sans grep préalable, ne pas en créer de nouvelles : pour du code neuf, consommer directement les tokens --bg/--text/--primary/... du DS.
Trois variables restent volontairement non-aliasées, propres à nav-carte :
--nav-primary-raw— triplet RGB (26, 34, 56) utilisé dans desrgba(var(--nav-primary-raw), x). Le DS n'expose pas de triplet RGB séparé, seulement des couleurs finales.--nav-primary-solid— bleu nuit plein (#1a2238clair,#c8d2f0sombre). Différent de--primarydu DS, qui est le bleu nuit à 60 % d'opacité (utilisé pour les surfaces translucides).--nav-primary-solidsert au texte et aux éléments qui doivent rester lisibles en plein (logo, soulignement d'onglet actif).--nav-text-on-primary— texte de contraste, gardé en valeur littérale (#f8f6f1clair /#111520sombre) plutôt qu'aliasé sur--on-primarydu DS. Raison : cette variable sert de texte de contraste sur DEUX fonds différents dans le code existant —--nav-primary(translucide) ET--nav-primary-solid(opaque) — qui ont une légèreté inversée en mode sombre (le translucide rend sombre une fois mélangé au fond, le solid#c8d2f0est un lavande clair). Le rôle--on-primarydu DS ne colle qu'au premier cas ; l'aliaser cassait le contraste à une dizaine d'endroits (logo « AEP », boutons, bandeaux) en sombre. Si tu ajoutes un nouvel usage de cette variable, vérifie d'abord sur QUEL fond (translucide ou solid) le texte doit être lisible.
Dark mode : classe .dark posée sur <html> (document.documentElement.classList.add('dark'), persisté dans localStorage.aep_theme). theme/aep.css définit ses valeurs sombres sous le sélecteur :root.dark, :root[data-theme="dark"] — les deux formes sont acceptées, mais nav-carte n'utilise que .dark. Pas de bascule automatique par prefers-color-scheme (décision explicite : le site n'en a jamais eu, ça changerait ce que voient les visiteurs par défaut).
Grammaire des chips par dimension
Trois dimensions, trois traitements visuels — posés en B3-M1, pensés pour rester stables jusqu'à B4+ (nouvelles dimensions à ajouter, pas à réinventer) :
| Dimension | Variant | Rendu inactif | Rendu actif |
|---|---|---|---|
| Échelle (national/régional/local) | echelle |
outline, fond transparent | fond --primary plein, texte --on-primary |
| Fonction / hashtag | fonction (défaut) |
fond --bg-alt |
fond --primary plein, texte --on-primary |
| Posture (préparée pour B4, pas encore consommée) | posture |
outline safran (--accent) |
fond --accent plein, texte --on-accent sombre — jamais blanc sur safran (voir règle de contraste plus bas) |
Composants : components/Chip.vue (une chip, <button> ou <span> selon as) et components/ChipGroup.vue (conteneur role="group", flex-wrap, gap). Toujours les deux ensemble — jamais une chip nue hors groupe (accessibilité : le groupe porte l'aria-label qui dit ce qu'on filtre).
<ChipGroup aria-label="Filtrer par fonction">
<Chip
v-for="fn in fonctions"
:key="fn"
:label="fn"
variant="fonction"
:active="selected.includes(fn)"
@toggle="toggleFonction(fn)"
/>
</ChipGroup>
Pour une chip non cliquable (affichage seul, ex. liste de résultats) : as="span" — pas de type="button", pas d'aria-pressed, pas de handler @toggle nécessaire.
FicheMiniCard + useFicheAdapter — brancher une nouvelle source
composables/useFicheAdapter.ts expose toMini(source): MiniFiche, où MiniFiche = { id, nom, resume, chips, echelle?, geoloc, kind }. Aujourd'hui il distingue Org (NocoDB, page /) et StructureV2 (JSON statique reseaux-bifurcation.json, page /agences) via 'famille_principale' in source (seul champ propre à StructureV2).
Pour brancher une 3e source (B6+, ex. un nouveau type de fiche) :
- Ajoute son type à l'union
Org | StructureV2 | TonNouveauTypedans la signature detoMini. - Ajoute une garde de type (comme
isStructure) basée sur un champ qui n'existe QUE dans ton nouveau type — ne réutilise pasfamille_principaleou un champ ambigu. - Choisis l'ordre de repli du résumé :
buildResume([champA, champB, ...])prend le premier champ non vide, en garde la première phrase, tronque à 110 caractères sur un mot. Vérifie quels champs de ton type contiennent du texte descriptif avant de les ordonner. chips= 3 étiquettes maximum (fonctions, hashtags, ou l'équivalent pour ton type).geoloc=latitude != null && longitude != nullsi ton type a des coordonnées, sinontrue(pas de fiche à masquer côté carte).
components/FicheMiniCard.vue consomme uniquement MiniFiche (depuis B11, MiniFiche porte aussi etiquettes, chipsDimension, coords, date, ville, texte : voir §Vue fiches) — il n'a jamais besoin de connaître Org ou StructureV2. C'est ce découplage qui permet d'ajouter une source sans toucher au composant d'affichage.
Vue fiches (B11 02/10/2026, détail B12 03/10/2026) — VueFiches + useVueFiches
Remplace FichesPanel, NavSidebar, MobileSheet et les onglets Métropolitain / Outre-mer / Toutes les fiches de / et /agences (tous retirés). Spécification : 1 PROJETS/DEV/AEP/Cadrage/SPEC-vue-fiches.md (vault).
Trois fichiers, trois rôles.
| Fichier | Rôle | Testé par |
|---|---|---|
utils/vueFiches.ts |
logique pure : lireQuery / ecrireQuery, filtrerFiches, compterEtiquettes, trierFiches, regrouper, ordreFiches ; B12 : lireFiche, queryAvecFiche, positionFiche, modeOuverture / modeFermeture, empreinteListe |
node scripts/test-vue-fiches.mjs |
composables/useVueFiches.ts |
branche la logique sur la route ; rend un objet reactive (vf) |
navigateur |
components/VueFiches.vue (+ VueFichesFiltres.vue) |
barre d'outils, trois états, filtres, grille, poignée, feuille « Filtrer », panneau de détail (B12) | navigateur |
L'URL est la source de vérité. L'état se lit dans route.query (surveillée) et s'écrit par router.replace. Paramètres : vue (carte · mixte · fiches, absent = défaut par largeur), q, un paramètre par dimension (valeurs séparées par des virgules), adresse=sans, tri, groupe, et celui des sous-vues de carte (mode sur /, carte sur /agences). Un paramètre inconnu ou invalide est ignoré ; un paramètre que la vue ne gère pas (territoire, random) est recopié tel quel, fiche= aussi (un filtre changé garde la fiche ouverte). La recherche part dans l'URL 250 ms après la dernière touche.
États. Sans vue= : Mixte à partir de 1024 px, Fiches en dessous. Mixte n'existe pas sous 1024 px (il se lit Fiches). Avant le montage, la racine porte vf--auto et la CSS tranche seule, pour qu'un lien s'affiche sans saut. La poignée ‹ › bascule Mixte ↔ Carte ; le repli est retenu en sessionStorage (aep_vue_fiches_liste_repliee) et ne compte que si l'URL n'a pas de vue=. En état Fiches, la carte reste montée, invisible (Leaflet garde une taille) ; les cartes appellent invalidateSize par ResizeObserver.
Filtres : OU dans une dimension, ET entre dimensions. Compteur d'une chip = fiches qui portent l'étiquette parmi celles que laissent passer la recherche et les AUTRES dimensions ; 0 = chip grisée (disabled). Repli au-delà de 14 valeurs, dans l'ordre de la dimension (stable). Les filtres sont à UN endroit : panneau de gauche (Mixte), tête de grille (Fiches, ordinateur et tablette), feuille du bas (téléphone) ; en Carte, seules les pastilles actives, dans la barre. Une chip cliquée dans une carte-fiche ajoute le filtre (MiniFiche.chipsDimension).
Tri : pertinence (nombre d'étiquettes cochées, puis nom ; défaut dès qu'un filtre est actif), nom (défaut sinon), recent (date puis Id décroissant ; à n'offrir que si la source a une date). Regroupement : une fiche va sous sa PREMIÈRE étiquette de la dimension, jamais en double.
Détail et fiche à fiche (B12). Cliquer une carte-fiche (ou une épingle : la page appelle vf.ouvrirFiche(id)) écrit fiche=<id> par router.push ; passer d'une fiche à l'autre (Précédente / Suivante, ← →, balayage au téléphone) par router.replace. Fermer (✕, « ‹ N fiches », Échap) revient en arrière si l'entrée vient de la page et que la liste n'a pas changé depuis, sinon retire fiche= sur place (lien partagé, filtre changé fiche ouverte) : le retour du téléphone ferme la fiche, jamais la page. La fiche est cherchée dans TOUTES les fiches : hors du résultat, le panneau dit « Hors de vos filtres » et n'a pas de Précédente / Suivante. Panneau de 380 px en troisième colonne (ordinateur, dans les trois états ; la grille Fiches passe à 2 colonnes), 340 px sur tablette, plein écran par-dessus la vue au téléphone (barre, liste et carte en inert, fiche à fiche en pied). Le focus entre dans le panneau à l'ouverture et revient, à la fermeture, sur la carte-fiche de la dernière fiche lue ; la position de défilement de la liste est rendue. Actions du panneau : Ouvrir la page de la fiche (prop lien-page), Voir sur la carte (si la fiche a une épingle dans le résultat : Mixte sur ordinateur, Carte au téléphone ; la page lit vf.centrage et le passe à sa carte, prop centrage de NavMap / NavMapV2), Copier le lien (l'URL courante dit tout). Deux écritures d'URL dans le même tick s'enchaînent (la seconde part de la query en attente), elles ne s'écrasent plus.
Contrat.
<VueFiches :vf="vf" intention="Une ligne." libelle-recherche="Rechercher…" :pending="pending"
:lien-page="(f) => `/fiche/${f.id}`" @survol="…">
<template #carte> <!-- carte(s) de la page, une par sous-vue, v-show sur vf.etat.sousVue --> </template>
<template #detail="{ fiche, filtrer }"> <!-- contenu propre à la source ; filtrer(dimension, valeur) au clic d'une étiquette --> </template>
<template #vide> <!-- optionnel --> </template>
</VueFiches>
Contenus de détail en place : / → FicheDetail (compact, etiquettes-cliquables) + commentaires, l'Org venant de la liste (même enregistrement NocoDB que /api/fiche/:id) ; /agences → FicheReseauContenu (corps de l'ancienne FicheModalV2, une structure liée s'ouvre dans le même panneau). FicheModal et FicheModalV2 sont retirées ; la page /fiche/:id reste.
const vf = useVueFiches(fiches, config) : fiches = MiniFiche[] déjà adaptées, non filtrées (ref, computed ou getter) ; config: ConfigVueFiches = { dimensions, tris, groupes, etats, sousVues? } (types dans utils/vueFiches.ts). Le composant ne charge rien et ne connaît pas la carte. La page lit vf.idsResultat pour ne montrer sur la carte que le résultat courant, vf.survolId pour allumer l'épingle (et l'écrit au survol d'une épingle), vf.ficheId / vf.ficheOuverte / vf.position pour la fiche ouverte (B12), vf.largeur et vf.vueAffichee pour placer ses boutons flottants.
Brancher une troisième page (Codev en B12, Outils, vue Œuvres) :
- Adaptateur dans
useFicheAdapter.ts: rempliretiquettesavec une clé par dimension (= nom du paramètre d'URL),chipsDimension,coordssi la source en a (sinongeoloc: true). - Une
ConfigVueFichesdans la page : dimensions (variantefonctionpour la principale,echellepour la secondaire), tris (pas derecentsans date), groupes,etats(['carte', 'fiches']si la page n'a pas la place pour Mixte),sousVuessi la carte en a. - Le slot
#carte, et le slot#detail(contenu de la fiche ; une page sans contenu de détail affiche seulement les actions). Les épingles appellentvf.ouvrirFiche(id). - Ajouter des cas à
scripts/test-vue-fiches.mjssi la page introduit une règle nouvelle.
FicheMiniCard : racine <article>, toute la carte ouvre la fiche par un bouton étiré (::after) ; avec etiquettes-cliquables, les chips deviennent des boutons (impossible dans l'ancienne racine <button>). Props actif (liseré safran au survol d'épingle), etiquettes-actives. Résumé et « Sans localisation » en color-mix(--text 78 %) : --text-muted et --text-subtle échouaient au contraste.
Intention d'abord
Principe de design validé par Jules pour B3 : un panneau (ou une carte) s'ouvre sur une ligne d'intention avant l'outillage (filtres, compteur, grille). La ligne d'intention dit en une phrase ce que montre l'écran et le premier geste possible (« Toutes les structures d'entraide, y compris celles sans adresse sur la carte. Cochez une fonction pour filtrer. »). Dans VueFiches, l'intention ouvre la liste, avant les filtres et la grille. Ne pas remonter le compteur ou les chips au-dessus de l'intention, même pour gagner de la place.
Motif maillage (favicon)
Le favicon (public/favicon.svg, décliné en favicon-32.png, favicon-16.png, apple-touch-icon.png) dessine un petit graphe : un nœud central relié à cinq nœuds périphériques par des arêtes, sur fond bleu nuit avec des nœuds/arêtes safran. C'est un motif à décliner, pas une icône isolée — il préfigure les liens de graphe et les connexions entre fiches attendus en B4 (postures), B8 et B9 (structures liées, graphe de réseau déjà présent dans FicheModalV2 via structuresVoisines). Si tu dessines un composant de graphe ou de connexions entre fiches dans ces batchs, reprends ce vocabulaire visuel (nœuds pleins, arêtes fines, un nœud central mis en avant) plutôt que d'en inventer un nouveau.
Deux règles de contraste
- Texte sombre sur l'accent safran, jamais blanc.
--accent/--on-accent: le blanc sur#f5b342échoue le contrôle AA (décision DS du 20/07/2026).--on-accentest toujours une couleur sombre (#1a2238clair,#111520sombre). Vérifié au pixel sur la varianteposturedeChip.vue. --nav-text-on-primaryreste une valeur littérale, pas un alias — voir l'explication détaillée dans la section tokens ci-dessus. Si un nouveau composant a besoin de texte de contraste sur un fond bleu nuit, vérifie d'abord si ce fond est--nav-primary(translucide) ou--nav-primary-solid(opaque) avant de choisir la variable de texte : elles n'ont pas la même réponse en mode sombre.
Navigation par échelles S / M / XL (B10, 02/10/2026)
Source unique : utils/echelles.ts. Chaque échelle porte ses onglets (libelle, to, actif(route)) ; ORDRE_ECHELLES fixe l'ordre S → M → XL ; echelleDeRoute(route) rend le groupe et l'onglet courants, ou null hors groupe (Manifeste, À propos, Proposer, Signaler, fiche, /rag). Aucun libellé d'échelle ni d'onglet ne se recopie dans un composant : ajouter un onglet = une ligne dans ce fichier, et scripts/test-echelles-nav.mjs (une ligne de cas).
| Échelle | Accroche | Onglets |
|---|---|---|
| S | Partir de soi | Jobs (/trouver-du-taf) · Outils (/outils) |
| M | La communauté | Écosystème d'entraide (/) · Réseaux AEP (/agences) · Codev (/codev*) |
| XL | Contribuer | Recherche média (/media) · Publications (/media?tab=projets) |
Seuils (CSS des composants, pas de breakpoint Tailwind : 1280 et 1440 n'en sont pas) :
| Largeur | En-tête | Sous l'en-tête |
|---|---|---|
| ≥ 1440 | logo complet · trois groupes · Manifeste · À propos · Soutenir (contour safran) · + Proposer (plein safran) · sombre | — |
| 1280-1439 | idem, logo réduit au bloc « AEP » | — |
| 1024-1279 | logo complet · groupe courant seul · Manifeste · + Proposer · sombre · « Menu » | — |
| 768-1023 | logo réduit · groupe courant seul · Manifeste · + Proposer · sombre · « Menu » | — |
| < 768 | logo complet · sombre · + (icône) · ☰ | FilEchelle (28 px), sauf hors groupe |
Composants : NavEchelles.vue (barre ; chaque groupe est un <ul> étiqueté par son accroche via aria-labelledby, onglet courant en aria-current="page"), NavTiroir.vue (menu < 1280 : role="dialog", voile, Échap, focus piégé et rendu à l'élément d'ouverture, fermeture au changement de route et au passage à ≥ 1280), FilEchelle.vue (< 768, monté dans app.vue entre <header> et le conteneur de page, jamais dedans : / y est en overflow-hidden). Le lien de don vit dans utils/liens.ts (SOUTENIR_URL).
Texte atténué : color-mix(in srgb, var(--text) 78 %, transparent), pas --text-muted (3,7:1 sur blanc, sous AA à 0,8 rem) ni opacity (qui éteindrait aussi l'anneau de focus).
Signaler n'est plus dans la barre d'ordinateur : il reste dans le tiroir, dans chaque fiche, et en pied d'À propos.