# Design system Hublot (v3.3.1)

Source unique du design system Hublot. Ce dépôt (`hublot/design-system`) est la référence : il est servi tel quel par nginx (`Dockerfile`, `nginx.conf`) avec la page de revue, le catalogue et tous les fichiers chargeables. Les projets le consomment via `@hublot-io/core-ui/assets/design-system/…` (core-ui reprend ces fichiers ; aucun projet n'en garde de copie locale). Page de référence : Notion › Suivi développement › Design system Hublot. Artefact Claude Design System « Hublot » : la référence visuelle (brand book, tokens, composants) pour les maquettes.

## Dépôt et site

- `index.html` → `composants/` (la documentation : une page par composant, générée par `scripts/docs.mjs` depuis `docs/composants/*.md`), `review.html` (vitrine) et `catalogue.html` (inventaire). Tout fichier du dépôt est servi à son chemin (`tokens.css`, `dist/json/tokens.flat.json`…).
- Les fichiers générés (`tokens.css`, `dist/`, `vuetify.css`, `catalogue.html`, `composants/`) sont commités : l'image nginx ne construit rien. La CI (job `build`, runner podman) refuse un commit dont les fichiers générés ne sont pas à jour ou dont un contraste passe sous le seuil, construit l'image nginx et, sur `main` seulement, pousse l'image `registry.gitlab.hublot.io/hublot/design-system:<sha>`.
- En local : `docker build -t design-system . && docker run -p 8080:80 design-system`, ou simplement `python3 -m http.server 8765` dans le dossier.
- Déploiement : job `deploy` (manuel, sur `main`) qui applique `k8s/` (Deployment nginx, Service, Ingress nginx + certificat Let's Encrypt) dans le namespace `crm` du cluster de prod, avec la variable d'instance GitLab `KUBECONFIG` comme crm-front. Le site `https://design.hublot.io` se connecte à Keycloak comme `dashboard.hublot.io` : `auth.js`, chargé en tête de chaque page, masque la page jusqu'à la connexion (realm `crm`, client public `design-system`) et ne l'affiche qu'à un compte à adresse `@hublot.io` vérifiée (le realm contient aussi les comptes des magasins). Il n'agit que sur ce domaine : en local, rien ne change. C'est une porte d'entrée, pas un coffre : les fichiers restent lisibles par leur adresse, ce qui convient à un guide sans secret. Jamais indexé (`X-Robots-Tag: noindex`). Côté Keycloak : client `design-system` public (« Client authentication » désactivé), redirection `https://design.hublot.io/*`, origine web `https://design.hublot.io`. DNS : `design.hublot.io` en CNAME vers `k8s-prod.scw.hublot.io`.

**Ce qui change en v3 :** les tokens ont une source unique, `tokens/hublot.tokens.json` (format W3C Design Tokens), et un générateur, `scripts/build.mjs`, qui produit toutes les cibles. `tokens.css` n'est plus écrit à la main. Chaque stack (Vue 2 + Vuetify 2, Vue 3 + Vuetify 3, Nuxt/Tailwind, Android, iOS, WebView) importe la cible qui lui convient : voir [docs/PLATEFORMES.md](docs/PLATEFORMES.md). La migration Vuetify 2 → 3 est préparée : [docs/MIGRATION-VUETIFY-3.md](docs/MIGRATION-VUETIFY-3.md).

## Fichiers

| Fichier | Rôle | Écrit par |
| --- | --- | --- |
| `tokens/hublot.tokens.json` | **La source.** Couleurs, ombres, espacements, rayons, tailles, polices, styles de texte, mouvement, plans, points de rupture, avec leurs modes (thème clair/sombre, métier, densité) et leur usage. | la main |
| `scripts/build.mjs` | Génère tout ce qui suit et vérifie les contrastes WCAG (`--strict` fait échouer la CI sous le seuil). | la main |
| `tokens.css` (= `dist/css/tokens.css`) | Les variables CSS, thème `[data-theme]`, métier `[data-activity]`, densité `[data-density]`. | build |
| `bundle.css` | Les classes `hb-*` : boutons, champs (texte, liste déroulante, recherche, sélecteur de quantité, radio, interrupteur, case), grille de formulaire, texte éditable, badges, puces, avatars, cartes, tuiles, tableau (tri, sélection, totaux), listes de tâches, glisser-déposer, liste simple, accordéon, chronologie, onglets, contrôle segmenté, fil d'Ariane, pagination, étapes, progression, squelettes, alertes, toast, dialogue, menu, infobulle CSS, navigation, barre d'outils, barre de validation, pavé numérique, zone de dépôt. | la main |
| `bundle.js` | `window.Hublot` : formats (`formatEUR`, `formatKg`, `formatDate`…), modes (`setMode`, `getMode`, `token`), mouvement, `badgeTone`. Script classique, sans dépendance : commun à toutes les stacks web. | la main |
| `vuetify.scss` + `vuetify.css` | Habillage Vuetify 2 par les tokens (`vuetify.css` compilé pour `review.html` seulement). | la main / `npx sass` |
| `vuetify2/variables.scss` | Variables SASS de Vuetify 2 (vuetify-loader). | la main |
| `theme.js` (+ `theme.d.ts`) | Vue 2 + Vuetify 2 : `applyActivity`, `applyTheme`, `applyDensity`, plugin `HublotVuetify2` (`this.$hublot`). | la main |
| `vuetify3/settings.scss`, `vuetify3/overrides.scss`, `vuetify3/useHublotTheme.ts` | Vue 3 + Vuetify 3 : réglages SASS, habillage, composable de modes (utilisable sans Vuetify). | la main |
| `dist/vuetify3/themes.ts` | Les quatre thèmes Vuetify 3 (`poissonnerie-light`, `poissonnerie-dark`, `boucherie-light`, `boucherie-dark`). | build |
| `dist/scss/_tokens.scss` | Cartes SASS et fonctions `token()`, `space()`. | build |
| `dist/js/tokens.js` (+ `.d.ts`), `dist/json/*.json` | Valeurs résolues pour chaque combinaison de modes (tests, outils, rapport de contraste). | build |
| `dist/tailwind/preset.cjs` | Preset Tailwind (site v2 Nuxt). | build |
| `dist/android/` | `res/values*/colors.xml`, `dimens.xml` (jour/nuit, sw600dp) et `HublotTokens.kt` (Jetpack Compose : `HublotTheme`). | build |
| `dist/ios/HublotTokens.swift` | SwiftUI/UIKit : `Color.hublot(.brand, activity:)` dynamique clair/sombre, espacements, rayons, tailles. | build |
| `dist/claude-design/tokens.json` | Le `tokens.json` de l'artefact Claude Design System (à y publier après chaque changement de token). | build |
| `docs/composants/*.md` → `composants/*.html` | **La documentation** : une page par composant (quand l'utiliser, balisage à copier, variantes et états, accessibilité, équivalent Vuetify 2 et 3), avec les exemples rendus en direct dans les deux métiers, trois densités, deux thèmes. `scripts/docs.mjs` génère les pages. | la main / script |
| `catalogue.html` | Tout le contenu des fichiers, lisible dans un navigateur (`node catalogue.mjs`). | script |
| `review.html` | La vitrine (Vue et Vuetify depuis un CDN) : composants, deux métiers, trois densités, deux thèmes, maquette check-list. | la main |

## Modes

Trois attributs sur `<html>` (ou, pour le métier, sur une vue ou une carte), posés par `Hublot.setMode({ activity, theme, density })` :

- `data-theme` : `light` (défaut), `dark`, `auto` (suit le système). Mémorisé dans `localStorage['hublot-theme']`.
- `data-activity` : `poissonnerie` (défaut), `boucherie`. Rebranche les emplacements `brand*`, `accent*`, `nav*`, `info`, `chart-1`, `chart-5`. Un écran ne mélange pas les deux métiers.
- `data-density` : `normal` (magasin, site), `compact` (crm-front : 13,5 px, contrôles 34 px), `touch` (tablette en rayon : 17 px, contrôles 48 px, lignes 56 px, icônes 24 px). **Nouveau en v3** : la densité `touch` remplace la pose des classes `-touch` une à une ; ces classes restent valables pour forcer un élément hors densité.

## Règles

- Les couleurs et les tailles viennent des tokens : aucun hexadécimal ni taille en dur dans un écran, sur aucune plateforme. Les composants n'utilisent jamais une couleur métier directement : ils passent par `brand`, `accent`, `nav`.
- Une seule action principale par écran (`hb-btn-primary` ou `v-btn color="primary"`), libellés en casse de phrase, verbe d'abord.
- Annuler / Fermer en premier dans le DOM donc à gauche, l'action principale en dernier donc à droite : la barre d'actions (`v-card__actions`, `hb-card-foot`, `hb-dialog-actions`) pose l'espace entre les deux, sans spacer.
- Tout bouton à icône seule porte une infobulle et un `aria-label` ; un bouton à libellé n'en a pas.
- Les composants Vuetify (menus, sélecteurs, sélecteur de date, dialogues, puces) restent ceux de Vuetify : on ne les refait pas, on les habille (`vuetify.scss`, `vuetify3/overrides.scss`).
- Icônes neutres en `--ink-muted`, couleur métier à l'action ou au survol ; le rouge n'apparaît qu'au survol d'une suppression. En Boucherie, un bouton destructif reste en contour.
- Dans une liste, l'état vide est une ligne squelette (`hb-list-row--empty`), les boutons d'ajout juste dessous.
- Toute cible tactile fait au moins `touch-target` (44 px) ; un texte lisible fait au moins 4,5:1 sur son fond dans les deux thèmes (le build le vérifie : `dist/json/contrast.json`).
- Une animation a une raison ; `transform` et `opacity` seulement ; durées et courbes de `motion` (`--duration-*`, `--ease-*`, `Hublot.motion`) ; `prefers-reduced-motion` respecté.

## Dans un projet Vue 2 + Vuetify 2 (crm-front, magasin)

Imports une fois dans le point d'entrée, juste après les feuilles de Vuetify (sa réinitialisation `button { color: inherit }` écraserait sinon la couleur des `hb-btn-*`) :

```ts
import 'vuetify/dist/vuetify.min.css'
import '@hublot-io/core-ui/assets/design-system/tokens.css'
import '@hublot-io/core-ui/assets/design-system/bundle.css'
import '@hublot-io/core-ui/assets/design-system/vuetify.scss'
import '@hublot-io/core-ui/assets/design-system/bundle.js'
import { HublotVuetify2 } from '@hublot-io/core-ui/assets/design-system/theme'
import vuetify from './plugins/vuetify'

Vue.use(HublotVuetify2, { vuetify, activity: 'poissonnerie', density: 'compact' }) // crm-front ; magasin : 'normal'
// puis, au changement de rayon : this.$hublot.setActivity(shop.activity) ; bouton thème : this.$hublot.setTheme('dark' | 'light' | 'auto')
```

`index.html` charge Roboto 400, 500, 600, Paytone One et Roboto Mono. Les variables SASS : `@import '@hublot-io/core-ui/assets/design-system/vuetify2/variables'` dans `src/scss/variables.scss`.

## Dans un projet Vue 3 + Vuetify 3 (nouveaux fronts, migration de crm-front)

```ts
import 'vuetify/styles'
import '@hublot-io/core-ui/assets/design-system/tokens.css'
import '@hublot-io/core-ui/assets/design-system/bundle.css'
import '@hublot-io/core-ui/assets/design-system/vuetify3/overrides.scss'
import '@hublot-io/core-ui/assets/design-system/bundle.js'
import { createVuetify } from 'vuetify'
import { hublotVuetifyTheme } from '@hublot-io/core-ui/assets/design-system/dist/vuetify3/themes'
import { useHublotTheme } from '@hublot-io/core-ui/assets/design-system/vuetify3/useHublotTheme'

export const vuetify = createVuetify({ theme: hublotVuetifyTheme })
// App.vue : const hublot = useHublotTheme({ activity: 'poissonnerie', density: 'normal' }, useTheme())
```

Les mêmes classes `hb-*`, les mêmes crochets (`hb-topbar`, `hb-nav-drawer`, `hb-tabs-row`) qu'en Vuetify 2. Le site v2 (Nuxt, sans Vuetify) importe `tokens.css`, `bundle.css`, `bundle.js`, le preset Tailwind, et appelle `useHublotTheme()` dans un plugin client.

## Mettre à jour

1. Modifier `tokens/hublot.tokens.json` (un token : valeur, modes, `$description`), ou `bundle.css` / `vuetify.scss` / `vuetify3/overrides.scss` pour une forme. Un nouveau composant a sa page dans `docs/composants/` (en-tête `titre`, `famille`, `classes`, `vuetify2`, `vuetify3`, `statut`, `resume`, puis Markdown et blocs ```html rendus en direct).
2. `npm run build` (= `node scripts/build.mjs --strict` : refuse un contraste sous le seuil, puis `npx sass`, `node catalogue.mjs` et `node scripts/docs.mjs`).
3. Vérifier dans `review.html` (les deux métiers, les trois densités, les deux thèmes).
4. Publier `dist/claude-design/tokens.json` dans l'artefact Claude Design System si un token a changé.
5. MR sur ce dépôt, merge sur `main` (image nginx poussée par la CI), puis reprise des fichiers dans core-ui, tag de core-ui par la CI, montée de version de `@hublot-io/core-ui` dans chaque projet consommateur. Règles de version et d'obsolescence : [docs/GOUVERNANCE.md](docs/GOUVERNANCE.md). Historique : [CHANGELOG.md](CHANGELOG.md).
