# Design system : jetons, thème, catalogue

Trois couches, et une règle : **aucune couleur en dur dans un composant.**

| Couche               | Fichier                             | Ce qu'elle porte                                                    |
| -------------------- | ----------------------------------- | ------------------------------------------------------------------- |
| Charte de l'instance | `instances/<id>/research.config.ts` | 4 couleurs (`primary`, `accent`, `ink`, `surface`), 2 polices, logo |
| Thème Mantine        | `src/theme/index.ts`, `colors.ts`   | Échelles de 10 nuances, typographie, rayons, variantes accessibles  |
| Jetons CSS           | `src/theme/tokens.css`              | Fonds, texte, traits, anneau de mise au point, mesure de lecture    |

## La charte pilote tout le reste

`brand.colors` ne contient que quatre valeurs. Les échelles Mantine en sont **calculées**
(`colorScale`) : la nuance 6 est la couleur exacte, 0 à 5 sont éclaircies vers le blanc, 7 à 9
assombries. Les variantes `light` et `outline` prennent la nuance 9 pour tenir 4,5:1, quelle que
soit la charte fournie (`variantColorResolver`).

Changer l'apparence d'une instance, c'est donc changer quatre hexadécimaux — pas retoucher des
composants.

## Les jetons CSS

`src/theme/tokens.css` est la source unique des valeurs qui ne passent pas par Mantine. Le
catalogue les liste et les montre : story **Fondations / Charte → Jetons**.

`--surface` fait exception : la mise en page racine l'écrase sur `<body>` avec la couleur de
l'instance. La valeur du fichier sert de repli, notamment dans Storybook.

Avant cette mise au propre, trois fonds blancs étaient écrits en dur (`bg="white"`,
`rgba(255,255,255,0.96)` pour l'en-tête) : une instance à la charte colorée obtenait un site
partiellement blanc. C'est corrigé ; le linter ne l'empêche pas encore, c'est une discipline de
relecture.

## Le catalogue de composants

```bash
pnpm storybook          # http://localhost:6006
pnpm build-storybook    # sortie statique dans storybook-static/
```

Storybook monte exactement l'enveloppe de l'application (`AppProviders` : thème Mantine +
dictionnaire), si bien qu'une story rend ce que rend une page.

- **Fondations / Charte** : échelles de couleur calculées, jetons, typographie, grilles.
- **Composants** : cartes, en-tête de page, fil d'Ariane, état vide, liens et boutons.

Deux réglages utiles :

- la bascule de langue dans la barre d'outils rend la story en français ou en anglais ;
- `NEXT_PUBLIC_RESEARCH_INSTANCE=paris1 pnpm storybook` affiche l'autre charte — c'est le moyen le
  plus rapide de vérifier qu'un composant ne dépend pas des couleurs de Saclay.

L'extension d'accessibilité (`@storybook/addon-a11y`) est active : chaque story affiche le rapport
axe de son propre rendu.

## Visualisation et tableau de bord interne

`packages/analytics-ui` porte les composants de graphique (Mantine + Recharts). Il ne collecte
rien — c'est `packages/analytics` qui collecte — et il ne connaît pas les données du projet : on
lui passe des tableaux de valeurs.

Le tableau de bord qui s'en sert est `/admin` : couverture des fiches publiées champ par champ,
état des sources, lacunes à arbitrer, couverture à la moisson par source, et conformité à la
référence figée.

```bash
NEXT_PUBLIC_ADMIN=true pnpm dev:saclay   # puis http://localhost:3000/admin
```

**Sans cette variable, la route répond 404 et ne contient aucune donnée** : le build produit bien
un fichier à cette adresse, mais c'est la page « introuvable », pas le tableau de bord — vérifié
sur la sortie de build (7 Ko, rien du contenu). La page montre les écarts avec le RNSR et les liens morts — rien de personnel,
mais rien qui doive se trouver sur le site public avant arbitrage. `robots.txt` l'exclut aussi,
par ceinture et bretelles.

Trois règles tenues dans ces composants, et leur raison :

- **une seule teinte par graphique.** La longueur encode déjà la grandeur ;
- **un statut n'est jamais une couleur seule.** Le vert et le rouge employés ici sont à ΔE 4,1 en
  deutéranopie : chaque statut porte une icône et un mot ;
- **deux jeux de couleurs de statut** : l'un pour les aplats (3:1 contre la surface), l'autre,
  plus sombre, pour le texte (4,5:1). Mesuré, pas estimé — la première version échouait à l'audit
  axe sur huit badges.

Chaque graphique a une **bascule « Tableau »** et un `role="img"` résumant ses valeurs : la page
reste lisible sans voir les couleurs.

## Un piège de composant serveur

`Table.Thead`, `Table.Td` et les autres sous-composants Mantine **ne traversent pas la frontière
serveur** : depuis un composant serveur, ce sont des propriétés statiques d'une référence de
module client, donc `undefined`, et le rendu échoue sur « Element type is invalid ». Tout usage de
`Table.*` vit dans un fichier marqué `'use client'` — c'est la raison d'être de
`src/app/admin/GapsTable.tsx`.

## Écrire une story

Les données d'exemple vivent dans `src/components/data-display/fixtures.ts` et imitent les fiches
réelles — textes longs, acronymes, ordres de grandeur. Une story qui ne montre que le cas idéal ne
sert à rien : `cards.stories.tsx` rend aussi une fiche sans acronyme, sans présentation et à
compteurs nuls, et une grille où les écarts de hauteur se voient.
