Files
lifetrack/docs/design/ux-pages.md
T
MeeJayandClaude Opus 5 93f0689c1e Initial import: LifeTrack v1 (santé, vape, finances)
Tracker de vie auto-hébergé : suivi poids/calories/sport avec planning de
pesées, sevrage tabac (vape) avec modèle de coût DIY et économies, et
finances personnelles avec import de relevés bancaires.

Architecture : FastAPI + SQLAlchemy 2.0 + PostgreSQL 16, React 18 + TS +
Vite + Tailwind + ECharts, déploiement Docker Compose. Modules
auto-découverts des deux côtés (pkgutil / import.meta.glob) et framework
de connecteurs à deux voies (importeurs de fichiers + ingestion JSON)
pour brancher de nouvelles sources sans toucher au noyau.

Validé : 292 tests pytest, tsc + vite build, contrat API/web vérifié
contre le schéma OpenAPI, et déploiement Docker réel sur PostgreSQL 16
(28 tables, SPA servie par nginx, wizard de premier démarrage).

Documentation : README.md, docs/GUIDE.md, CONVENTIONS.md, et les
documents de conception et de recherche dans docs/.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 10:48:57 +02:00

873 lines
66 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LifeTrack — Spécification UX complète (page par page)
> **Document de référence pour l'implémentation.** Prose en français, identifiants de code en anglais.
> Stack imposée : React 18 + TypeScript + Vite + TailwindCSS + Apache ECharts. UI 100 % en français, thème sombre par défaut.
> Ce document est exhaustif : les agents d'implémentation ne feront **aucune** recherche complémentaire.
---
## Table des matières
1. [Principes globaux et layout](#1-principes-globaux-et-layout)
2. [Formatage français (nombres, dates, unités)](#2-formatage-français)
3. [Sémantique des couleurs](#3-sémantique-des-couleurs)
4. [Design system (tokens Tailwind, composants, palette charts)](#4-design-system)
5. [Composants transverses](#5-composants-transverses)
6. [Configuration ECharts commune](#6-configuration-echarts-commune)
7. [Page — Tableau de bord](#7-page--tableau-de-bord)
8. [Page — Poids & Objectif](#8-page--poids--objectif)
9. [Page — Nutrition](#9-page--nutrition)
10. [Page — Activité & Sport](#10-page--activité--sport)
11. [Page — Balance énergétique](#11-page--balance-énergétique)
12. [Page — Vape](#12-page--vape)
13. [Page — Finances](#13-page--finances)
14. [Page — Imports](#14-page--imports)
15. [Page — Réglages](#15-page--réglages)
16. [États vides (empty states) — récapitulatif](#16-états-vides)
17. [Accessibilité](#17-accessibilité)
---
## 1. Principes globaux et layout
### 1.1 Structure générale
```
┌────────────┬──────────────────────────────────────────────┐
│ │ Topbar : titre de page + PeriodSelector + │
│ Sidebar │ actions rapides + menu utilisateur │
│ (nav) ├──────────────────────────────────────────────┤
│ │ Contenu : grille de KpiCard, ChartCard, │
│ │ tables, sections │
└────────────┴──────────────────────────────────────────────┘
```
- **Sidebar** (desktop ≥ 1024 px) : largeur `260px`, repliable en mode icônes `72px` (bouton chevron en bas de la sidebar, état persisté dans `localStorage`). Fond `bg-base` (`#0D0D0D`), séparée du contenu par une bordure hairline.
- **Entrées de navigation** (ordre fixe, icônes Lucide entre parenthèses) :
1. **Tableau de bord** (`layout-dashboard`) — route `/`
2. **Poids & Objectif** (`scale`) — route `/poids`
3. **Nutrition** (`utensils`) — route `/nutrition`
4. **Activité & Sport** (`footprints`) — route `/activite`
5. **Balance énergétique** (`flame`) — route `/balance`
6. **Vape** (`cloud`) — route `/vape`
7. **Finances** (`wallet`) — route `/finances`
8. **Imports** (`upload`) — route `/imports`
9. **Réglages** (`settings`) — route `/reglages`
- Item actif : fond `bg-surface-2`, barre verticale `3px` couleur `accent` (#3987E5) à gauche, texte `text-primary`. Items inactifs : `text-secondary`, hover `bg-surface-2/50`.
- En bas de sidebar : logo + version + bouton repli.
- **Topbar** : hauteur `56px`, contient (gauche → droite) : titre de la page (h1, 18 px semibold), `PeriodSelector` (voir §5.1), bouton **« + Ajouter »** (menu déroulant d'actions rapides, voir §5.6), avatar/menu utilisateur (Profil, Réglages, Déconnexion).
### 1.2 Responsive
Breakpoints Tailwind standard : `sm` 640, `md` 768, `lg` 1024, `xl` 1280, `2xl` 1536.
| Élément | Mobile (< 768) | Tablette (7681023) | Desktop (≥ 1024) |
|---|---|---|---|
| Navigation | Barre inférieure fixe 5 items : Tableau de bord, Poids, Nutrition, Vape, **« Plus »** (bottom-sheet listant les autres pages) | Sidebar repliée (icônes) | Sidebar complète |
| PeriodSelector | Menu déroulant compact (icône calendrier + libellé court) | Segmented control | Segmented control |
| Grille KPI | 2 colonnes | 3 colonnes | 4 à 6 colonnes |
| ChartCard | Pleine largeur, hauteur min `240px` | 12 colonnes | Grille 2 colonnes (graphique principal en pleine largeur) |
| Tables | Défilement horizontal **dans la carte** (`overflow-x-auto`), colonnes clés épinglées à gauche (Date, Libellé) ; jamais de scroll horizontal de page | idem | Table complète |
| Ajout rapide | FAB `+` en bas à droite (au-dessus de la nav), ouvre le menu d'actions rapides | Bouton topbar | Bouton topbar |
- Toutes les zones tactiles ≥ `44px`. Les graphiques restent lisibles à 360 px de large (labels d'axe X inclinés ou échantillonnés via `axisLabel.interval: 'auto'`).
- Les modales deviennent des **bottom-sheets** plein écran sur mobile.
### 1.3 Thème
- **Sombre par défaut.** Un thème clair existe (Réglages → Application) mais le sombre est la référence de conception. Tout hex de ce document est donné pour le fond sombre.
- `color-scheme: dark` sur `:root[data-theme="dark"]` ; bascule via attribut `data-theme` (`dark` | `light` | `auto`).
---
## 2. Formatage français
Toutes les valeurs affichées passent par des helpers centralisés (`src/lib/format.ts`) basés sur `Intl` avec locale `fr-FR`. Le séparateur de milliers est l'**espace fine insécable** (U+202F, produite nativement par `Intl.NumberFormat('fr-FR')`), le séparateur décimal est la **virgule**.
| Type | Helper | Exemple |
|---|---|---|
| Monnaie | `formatCurrency(v)``Intl.NumberFormat('fr-FR', {style:'currency', currency:'EUR'})` | `1 234,56 €` · `45,90 €` |
| Poids | `formatWeight(v)` — 1 décimale | `82,4 kg` |
| Calories | `formatKcal(v)` — entier groupé | `1 850 kcal` |
| Millilitres | `formatMl(v)` — 1 décimale | `4,2 ml` |
| Nicotine | `formatMg(v)` — 1 décimale | `12,6 mg` |
| Distance | `formatKm(v)` — 2 décimales | `5,25 km` |
| Pas | `formatSteps(v)` — entier groupé | `9 542` |
| Pourcentage | `formatPercent(v)` — espace avant % | `82 %` |
| Grammes (macros) | `formatG(v)` — entier | `132 g` |
| Durée | `formatDuration(min)` | `1 h 05` · `45 min` |
| Date | `formatDate(d)``dd/MM/yyyy` | `13/08/2026` |
| Date + heure | `formatDateTime(d)``dd/MM/yyyy HH:mm` | `13/08/2026 19:30` |
| Date courte (axes) | `formatDateShort(d)``dd/MM` | `13/08` |
| Mois (axes) | `formatMonth(d)` | `août 2026` (axe : `août 26`) |
| Semaine (axes) | `formatWeek(d)` | `Sem. 33` (tooltip : `du 10/08 au 16/08`) |
| Nombre signé | `formatSigned(v)` — signe explicite, « » U+2212 | `+0,3 kg` · `450 kcal` |
Règles :
- **Saisie** : les champs numériques acceptent la virgule **et** le point comme séparateur décimal (normalisation à la volée).
- **Stockage** : UTC en base ; affichage converti en `Europe/Paris` côté client (le backend renvoie de l'ISO 8601 UTC).
- « Aujourd'hui » et « Hier » remplacent la date dans les listes quand pertinent (journal nutrition, dernières transactions).
- Alignement : montants et nombres **alignés à droite** dans les tables, avec `font-variant-numeric: tabular-nums`.
---
## 3. Sémantique des couleurs
Deux familles distinctes : la **palette de séries** (identité, §4.4) et les **couleurs sémantiques** (polarité/état). Ne jamais utiliser une couleur sémantique comme couleur de série ordinaire.
| Signification | Couleur (fond sombre) | Usage |
|---|---|---|
| **Positif / favorable** — déficit calorique, économies vape, cashflow positif, budget respecté, perte de poids (si l'objectif est de perdre) | `#0CA30C` (`semantic-positive`) | Barres de balance nette en déficit, aire « Économies cumulées », deltas KPI favorables, cashflow > 0 |
| **Négatif / défavorable** — surplus calorique, dépassement de budget, cashflow négatif, reprise de poids | `#D03B3B` (`semantic-negative`) | Barres de surplus, budget > 100 %, deltas KPI défavorables |
| **Avertissement** — budget entre 80 et 100 %, résistance en fin de vie, import partiel | `#FAB219` (`semantic-warning`) | Badges, jauges, statuts |
| **Sérieux** (entre warning et negative) | `#EC835A` (`semantic-serious`) | Réservé aux statuts d'import et alertes |
| **Neutre / information** | `#3987E5` (slot 1 de la palette) | Liens, éléments actifs, info |
Règles impératives :
- **Direction consciente de l'objectif** : pour le poids, le vert signifie « va dans le sens de l'objectif ». Si `goal.direction = lose`, une variation négative est verte ; si `gain`, c'est l'inverse. Centraliser dans un helper `deltaTone(value, goalDirection)`.
- Une couleur sémantique n'est **jamais seule** porteuse de sens : elle est toujours accompagnée d'un signe (`+`/``), d'une icône (`trending-down`, `alert-triangle`…) ou d'un libellé.
- Montants dans les tables Finances : dépenses en `text-primary` avec signe ``, revenus en `semantic-positive` avec signe `+` (le signe porte l'info, la couleur renforce).
---
## 4. Design system
### 4.1 Tokens Tailwind (extrait de `tailwind.config.ts`)
```ts
// tailwind.config.ts — theme.extend
colors: {
base: '#0D0D0D', // fond de page
surface: '#1A1A19', // fond des cartes / graphiques
'surface-2': '#242423', // hover, éléments imbriqués, inputs
border: 'rgba(255,255,255,0.10)', // bordure hairline
ink: {
DEFAULT: '#FFFFFF', // text-primary
secondary:'#C3C2B7',
muted: '#898781', // labels d'axes, placeholders
},
grid: '#2C2C2A', // lignes de grille des graphiques
axis: '#383835', // ligne de base / axe
accent: '#3987E5',
semantic: {
positive: '#0CA30C',
negative: '#D03B3B',
warning: '#FAB219',
serious: '#EC835A',
},
chart: {
1: '#3987E5', 2: '#D95926', 3: '#199E70', 4: '#C98500',
5: '#D55181', 6: '#008300', 7: '#9085E9', 8: '#E66767',
},
},
borderRadius: { card: '12px' },
fontFamily: { sans: ['system-ui', '-apple-system', '"Segoe UI"', 'sans-serif'] },
```
Thème clair (non prioritaire, mêmes rôles) : `base #F9F9F7`, `surface #FCFCFB`, `ink #0B0B0B`, `ink-secondary #52514E`, `border rgba(11,11,11,0.10)`, `grid #E1E0D9`, palette charts clair : `#2A78D6, #EB6834, #1BAF7A, #EDA100, #E87BA4, #008300, #4A3AA7, #E34948`, `semantic-positive` texte : `#006300`.
### 4.2 Composant `Card`
Base de **toutes** les cartes (KPI, graphiques, tables, sections de réglages) :
- Fond `bg-surface`, `rounded-card` (12 px), bordure `1px solid border`, padding `16px` (mobile) / `20px` (desktop). Pas d'ombre portée (thème sombre) — la séparation vient du contraste fond/carte et de la bordure.
- En-tête optionnel : titre 14 px semibold `text-ink`, sous-titre 12 px `text-ink-muted`, zone d'actions à droite.
### 4.3 `KpiCard`
```
┌──────────────────────────────┐
│ LIBELLÉ (12px, muted, caps) │
│ 82,4 kg (28px, semibold) │
│ ▼ 0,4 kg sur 7 j (12px) │ ← delta coloré + icône
│ [sparkline optionnelle 40px] │
└──────────────────────────────┘
```
- Valeur principale : 28 px desktop / 24 px mobile, `text-ink`.
- Delta : icône `trending-up`/`trending-down` + valeur signée, colorée via `deltaTone`.
- Variante avec **jauge de progression** (barre 6 px arrondie) pour les KPI « X / budget ».
- Toute la carte est cliquable quand elle renvoie vers une page de détail (curseur pointer + hover `bg-surface-2/40`).
### 4.4 Palette de graphiques (fond sombre — **validée CVD, ne pas réordonner**)
L'ordre des slots est un mécanisme de sécurité daltonisme (validé par script : pire paire adjacente CVD ΔE 8,4 ; vision normale 19,3 ; tous les slots ≥ 3:1 de contraste sur `#1A1A19`). **Attribution fixe par entité — jamais recyclée, jamais réassignée quand une série est filtrée.**
| Slot | Hex | Attribution LifeTrack (identité fixe) |
|---|---|---|
| `chart-1` bleu | `#3987E5` | Poids (tendance), dépense énergétique (kcal out), distance, solde total |
| `chart-2` orange | `#D95926` | Apports caloriques (kcal in), kcal actives |
| `chart-3` vert d'eau | `#199E70` | Protéines, pas quotidiens |
| `chart-4` jaune | `#C98500` | Glucides, coût vape |
| `chart-5` magenta | `#D55181` | Lipides, nicotine |
| `chart-6` vert | `#008300` | (réservé — éviter près de `semantic-positive`) |
| `chart-7` violet | `#9085E9` | Vape (ml), poids théorique |
| `chart-8` rouge | `#E66767` | Dernier recours (jamais pour un sens « négatif ») |
- Variantes d'une même entité (ex. projection du poids) : **teintes séquentielles du même bleu**`#86B6EF` (clair), `#3987E5` (base), `#1C5CAB` (foncé) — jamais un nouveau slot.
- Catégories de dépenses (Finances) : slots 1→7 dans l'ordre, au-delà **regroupement « Autres »** en `ink-muted` (`#898781`). Pour les donuts et le sankey (formes où toutes les paires se comparent), limiter à 7 catégories + « Autres ».
- Chaque catégorie financière reçoit un slot **à sa création** (stocké en base) → couleur stable dans le temps.
- ≥ 2 séries ⇒ **légende toujours affichée** ; 1 série ⇒ pas de légende (le titre nomme la série).
- Jamais de valeur numérique sur chaque point : labels directs **sélectifs** (dernier point, max, min) uniquement.
- **Jamais de double axe Y.** Deux mesures d'échelles différentes = deux graphiques empilés partageant le même axe X et le même zoom (`echarts.connect`).
### 4.5 Boutons et champs
- Bouton primaire : fond `accent`, texte blanc, `rounded-lg`, hauteur 40 px. Secondaire : fond `surface-2`, bordure hairline. Destructif : fond `semantic-negative`. Ghost : texte `accent`.
- Inputs : fond `surface-2`, bordure hairline, focus ring `accent`, hauteur 40 px, labels au-dessus (12 px `text-secondary`), erreurs en 12 px `semantic-negative` sous le champ.
- Selects natifs stylés + combobox avec recherche pour listes longues (catégories, aliments).
---
## 5. Composants transverses
### 5.1 `PeriodSelector` (sélecteur de période)
- Segmented control : **`7 j` · `30 j` · `90 j` · `1 an` · `Tout` · `Personnalisé`**. Option active : fond `surface-2`, texte `text-ink` ; inactives `text-secondary`.
- `Personnalisé` ouvre un popover avec deux champs date (`dd/MM/yyyy`, datepicker fr, lundi premier jour de semaine) + raccourcis « Ce mois-ci », « Le mois dernier », « Cette année ». Libellé affiché ensuite : `01/06/2026 13/08/2026`.
- Par défaut : **30 j** partout, sauf Finances (**Ce mois-ci**) et Imports (pas de période).
- La période est **globale à la page**, persistée par page (`localStorage`, clé `period:<route>`) et reflétée dans l'URL (`?periode=30j` ou `?du=2026-06-01&au=2026-08-13`).
- Chaque `ChartCard` peut surcharger localement sa période (menu ⋯), la surcharge est signalée par un badge sur la carte.
### 5.2 `ChartCard`
`Card` + en-tête standard : titre du graphique, sous-titre optionnel (période effective), actions à droite : menu `⋯` avec **« Voir les données »** (bascule le graphique en table triable des mêmes valeurs — obligatoire, c'est la vue accessible), **« Exporter PNG »**, **« Exporter CSV »**, **« Plein écran »**. Hauteur par défaut du canvas : 280 px (mini-graphes du tableau de bord : 120 px ; graphiques principaux : 360 px).
### 5.3 `DataTable`
- En-têtes triables (flèche), lignes hauteur 44 px, zébrage désactivé (bordures hairline entre lignes), hover `surface-2/40`.
- Pagination 20 lignes (50/100 au choix), pied « 120 sur 254 ».
- Barre de filtres au-dessus, **sur une seule ligne** (wrap sur mobile) ; champ recherche à gauche, filtres à droite.
- Ligne vide → composant `EmptyState` (voir §16).
### 5.4 `EmptyState`
Illustration légère (icône 48 px `ink-muted`), titre 16 px, texte d'aide 14 px `text-secondary`, **1 à 2 boutons d'action** (primaire = action de saisie ou d'import). Textes exacts par page en §16.
### 5.5 Modales
Overlay `rgba(0,0,0,0.6)`, carte centrée `max-w-md` (formulaires rapides) ou `max-w-2xl` (règles, mapping d'import). Titre + croix de fermeture. Boutons en pied : « Annuler » (ghost) + action primaire. `Esc` ferme, `Entrée` valide les formulaires à un champ principal. Sur mobile : bottom-sheet plein écran.
### 5.6 Menu « + Ajouter » (actions rapides globales)
Disponible partout (topbar / FAB mobile). Items :
1. **« Pesée »** → modale Quick-add poids (§8.5)
2. **« Aliment »** → modale Quick-add aliment (§9.5)
3. **« Recharge vape »** → modale Quick-add recharge (§12.5)
4. **« Résistance changée »** → action un clic + toast (§12.6)
5. **« Séance de sport »** → modale séance (§10.5)
6. **« Transaction »** → modale transaction manuelle (§13.6)
7. **« Importer un fichier »** → route `/imports`
Raccourcis clavier : `a` ouvre le menu ; `p` pesée ; `n` aliment ; `v` recharge.
### 5.7 Toasts
En bas à droite (desktop) / haut (mobile). Succès : « Pesée enregistrée ✓ » avec lien « Annuler » (undo 5 s). Erreur : `semantic-negative` + détail.
---
## 6. Configuration ECharts commune
Fichier `src/lib/echarts.ts` : enregistrement des composants nécessaires (tree-shaking), locale `FR` d'ECharts, et un thème `lifetrack-dark` :
```ts
// Theme ECharts "lifetrack-dark" (extrait)
{
backgroundColor: 'transparent',
color: ['#3987E5','#D95926','#199E70','#C98500','#D55181','#008300','#9085E9','#E66767'],
textStyle: { color: '#C3C2B7', fontFamily: 'system-ui, "Segoe UI", sans-serif' },
axisLine: { lineStyle: { color: '#383835' } },
splitLine: { lineStyle: { color: '#2C2C2A' } },
axisLabel: { color: '#898781', fontSize: 11 },
legend: { textStyle: { color: '#C3C2B7' }, icon: 'circle', itemWidth: 8, itemHeight: 8, top: 0 },
tooltip: {
backgroundColor: '#1A1A19', borderColor: 'rgba(255,255,255,0.10)',
textStyle: { color: '#FFFFFF' }, padding: [8, 12],
},
}
```
Règles communes à **tous** les graphiques :
- `grid: { left: 8, right: 16, top: 36, bottom: 8, containLabel: true }` (bottom 44 si `dataZoom` slider).
- **Tooltip** : `trigger: 'axis'` avec `axisPointer: { type: 'line' }` pour les séries temporelles ; `trigger: 'item'` pour donut/sankey/barres horizontales. Formatter systématiquement en français via les helpers de §2 (date complète en tête, puis `● Série valeur` par ligne, valeurs alignées à droite).
- **Zoom** : tous les graphiques temporels reçoivent `dataZoom: [{ type: 'inside' }]` (molette + pincement) ; les graphiques « principaux » de page (poids, transactions par mois, ml vape) ajoutent `{ type: 'slider', height: 20 }`.
- Lignes : `width: 2`, `symbol: 'circle'`, `symbolSize: 6`, `showSymbol: false` (symboles visibles au hover seulement) ; `smooth: 0.2` maximum, jamais de lissage exagéré.
- Barres : `barMaxWidth: 28`, coins arrondis côté extrémité de donnée uniquement (`borderRadius: [4,4,0,0]` vers le haut, inversé vers le bas), **espace de 2 px** entre segments empilés (`itemStyle.borderColor: '#1A1A19', borderWidth: 1`).
- Aires : dégradé vertical de la couleur de série à 25 % → 0 % d'opacité.
- Axe Y : commence à 0 pour les barres ; pour le poids (ligne), `min`/`max` auto avec marge (`scale: true`).
- `markLine` (budgets, objectifs) : pointillés `[4,4]`, couleur `#C3C2B7`, label au bout à droite (ex. « Budget 1 800 »), `symbol: 'none'`.
- Pas d'animation à la mise à jour de période > 300 ms (`animationDuration: 300`).
- Chaque graphique expose `aria-label` descriptif et l'alternative « Voir les données » (§5.2).
---
## 7. Page — Tableau de bord
**Route** `/` · **Titre** « Tableau de bord » · **Période par défaut** : 30 j (s'applique aux mini-graphes ; les KPI « aujourd'hui » et « ce mois-ci » l'ignorent).
**Objectif UX** : une vue croisée de tous les modules en un écran, chaque bloc cliquable vers sa page de détail. Composé d'une rangée de 6 KPI, d'une rangée d'actions rapides, puis d'une grille de 6 **cartes-modules** avec mini-graphe.
### 7.1 KPI (rangée 1 — 6 cartes, 2×3 sur mobile)
| # | Libellé | Valeur | Sous-texte | Couleur delta |
|---|---|---|---|---|
| 1 | **Poids actuel** | `82,4 kg` (dernière pesée) | `▼ 0,4 kg sur 7 j` (delta de la tendance EMA) | `deltaTone` selon objectif |
| 2 | **Calories aujourd'hui** | `1 450 / 1 800 kcal` | jauge + `Reste 350 kcal` (ou `Dépassement de 120 kcal` en rouge) | vert ≤ budget, rouge > |
| 3 | **Déficit cumulé (30 j)** | `12 450 kcal` | `≈ 1,6 kg théoriques` | vert si déficit |
| 4 | **Vape aujourd'hui** | `3,8 ml` | `≈ 11,4 mg de nicotine` | neutre |
| 5 | **Économies vape** | `1 245,80 €` | `depuis le 15/03/2025` | toujours vert |
| 6 | **Dépenses du mois** | `1 234,56 € / 1 500,00 €` | jauge + `82 % du budget global` | vert < 80 %, jaune 80100 %, rouge > 100 % |
Si un module n'a pas de données, sa KpiCard affiche `—` + lien « Configurer ».
### 7.2 Actions rapides (rangée 2)
Boutons pleine largeur sur une ligne : **« + Pesée » · « + Aliment » · « + Recharge vape » · « Résistance changée » · « + Séance » · « Importer »** — mêmes actions que §5.6, en accès direct.
### 7.3 Cartes-modules (grille 2 colonnes desktop, 1 colonne mobile)
Chaque carte : titre + valeur clé + mini-graphe ECharts (120 px, sans axe Y visible, axe X en dates courtes, tooltip actif, pas de zoom) + lien « Voir le détail → ».
1. **« Poids »** — mini-line : tendance EMA (bleu `chart-1`, 2 px) + `markLine` horizontale objectif (pointillés muted, label « Objectif 78,0 kg »). Période sélectionnée.
2. **« Nutrition »** — mini-bar 7 derniers jours : kcal/jour, chaque barre colorée `semantic-positive` si ≤ budget du jour, `semantic-negative` sinon + `markLine` budget.
3. **« Balance énergétique »** — mini-bar : balance nette/jour (in out), barres vertes si < 0 (déficit), rouges si > 0, ligne zéro visible (`axisLine` sur y=0).
4. **« Vape »** — mini-line ml/jour (violet `chart-7`) + moyenne mobile 7 j en trait plein, valeurs brutes en points 40 % d'opacité.
5. **« Économies »** — mini-area cumulée verte (`#0CA30C`, dégradé) ; label direct sur le dernier point : `1 245,80 €`.
6. **« Finances »** — mini-bar 6 derniers mois : dépenses/mois (barres `chart-1`) + `markLine` budget mensuel global ; le mois courant est hachuré (mois incomplet, `decal` ECharts).
### 7.4 États particuliers
- Première visite (aucune donnée nulle part) : le tableau de bord est remplacé par un **écran d'accueil** : « Bienvenue sur LifeTrack 👋 » + 3 cartes d'onboarding : « Configurez votre profil » (→ `/reglages`), « Définissez votre objectif de poids » (→ `/reglages?tab=objectif`), « Importez vos premières données » (→ `/imports`).
---
## 8. Page — Poids & Objectif
**Route** `/poids` · **Titre** « Poids & Objectif » · **Période par défaut** : 90 j.
Définitions de calcul (implémentation) :
- `trendWeight` : EMA des pesées, `alpha = 0.1` (≈ tendance sur ~20 jours), calculée sur jours calendaires (interpolation : l'EMA n'avance que sur les jours avec pesée).
- `weeklyRate` : pente de la tendance sur 7 jours glissants, en kg/semaine.
- `projection` : régression linéaire de la tendance sur les 21 derniers jours, extrapolée jusqu'à `goal.targetWeight` (bornée à +365 j).
- `bmi = poids / taille²`.
### 8.1 KPI (6 cartes)
| Libellé | Valeur | Sous-texte |
|---|---|---|
| **Poids actuel** | `82,4 kg` | `Pesée du 13/08/2026` |
| **Tendance (EMA)** | `82,7 kg` | `▼ 0,4 kg sur 7 j` |
| **Rythme hebdo** | `0,45 kg/sem` | `Objectif : 0,50 kg/sem` (vert si |rythme| ≥ objectif dans le bon sens) |
| **IMC** | `26,3` | `Surpoids` (catégories : `< 18,5 Maigreur` · `18,525 Corpulence normale` · `2530 Surpoids` · `≥ 30 Obésité`) |
| **Objectif** | `78,0 kg` | `Reste 4,4 kg · 61 %` + jauge (départ → objectif) |
| **Atteinte estimée** | `24/10/2026` | `dans 10 semaines` (ou `— · rythme insuffisant` si la pente ne converge pas) |
### 8.2 Graphiques
**G1 — « Évolution du poids »** (principal, pleine largeur, 360 px)
- Type : `line` + `scatter` combinés, axe X `time` (dates), axe Y kg (`scale: true`, marge ±1 kg).
- Séries :
1. `Pesées` — scatter, symboles 6 px, `#3987E5` à 45 % d'opacité ;
2. `Tendance` — line 2 px `#3987E5`, sans symboles ;
3. `Projection` — line pointillée `[6,4]` `#86B6EF`, ne démarre qu'au dernier point de tendance, s'étend dans le futur ;
4. `Objectif``markLine` horizontale à `targetWeight`, pointillés, label « Objectif 78,0 kg » ;
5. `markPoint` discret à l'intersection projection/objectif avec label date estimée.
- Interactions : tooltip axe (Date · Pesée · Tendance), `dataZoom` inside + slider, légende (3 séries), clic-légende pour masquer les pesées brutes.
**G2 — « Rythme hebdomadaire »** (demi-largeur)
- Type : `bar`, une barre par semaine ISO, axe X semaines (`Sem. 31`…), axe Y kg/sem.
- Couleur par pièce (`visualMap.pieces`) : variation dans le sens de l'objectif → `semantic-positive`, sens inverse → `semantic-negative` (direction-aware §3).
- `markLine` au rythme cible (ex. `0,50`). Tooltip item : `Sem. 32 · du 03/08 au 09/08 — 0,45 kg`.
**G3 — « IMC »** (demi-largeur)
- Type : `line` (IMC de la tendance), axe Y IMC `min 16, max 35`.
- `markArea` de fond aux 4 bandes (opacité 6 % : bleu maigreur, vert normal, jaune surpoids, rouge obésité) + labels de bande à droite en 10 px muted. Tooltip : `13/08/2026 — IMC 26,3 (Surpoids)`.
**G4 — « Mensurations »** (pleine largeur, affiché seulement si ≥ 1 mesure existe)
- Type : `line` multi-séries, axe Y cm. Séries (ordre de palette) : `Tour de taille` (1), `Hanches` (2), `Poitrine` (3), `Bras` (4), `Cuisse` (5). Légende obligatoire + labels directs en bout de ligne. Tooltip axe. `connect` du zoom avec G1.
### 8.3 Table « Historique des pesées »
Colonnes : `Date` · `Poids` · `Tendance` · `Variation` (vs pesée précédente, signée et colorée) · `Note` · actions (✎ modifier, 🗑 supprimer avec confirmation). Tri par date desc. Bouton d'en-tête « + Ajouter une pesée ».
### 8.4 Carte « Objectif » (résumé)
Rappel de l'objectif configuré : `Départ 89,2 kg (12/01/2026) → Objectif 78,0 kg` + jauge de progression + « Modifier l'objectif » (→ Réglages, onglet Objectif).
### 8.5 Modale « Ajouter une pesée » (quick-add global)
Champs :
- **Poids (kg)** — numérique, pas 0,1, focus auto, pré-rempli avec la dernière valeur. Validation : 20300 kg.
- **Date** — datepicker, défaut aujourd'hui. **Heure** — optionnelle, défaut maintenant.
- **Note** — texte libre optionnel (placeholder : « ex. après le sport »).
- Section repliable **« Mensurations (optionnel) »** : Tour de taille, Hanches, Poitrine, Bras, Cuisse (cm, 1 décimale).
- Si une pesée existe déjà ce jour : avertissement « Une pesée existe déjà le 13/08/2026 (82,6 kg). Enregistrer remplacera cette valeur. » avec choix « Remplacer » / « Ajouter quand même ».
- Boutons : « Annuler » / « Enregistrer » → toast « Pesée enregistrée ✓ ».
---
## 9. Page — Nutrition
**Route** `/nutrition` · **Titre** « Nutrition » · **Période par défaut** : 30 j (graphes) ; le **journal** est journalier avec son propre navigateur de date.
Le budget kcal du jour vient de la Balance énergétique (§15.3) : `dailyBudget = TDEE targetDeficit`, ou valeur manuelle.
### 9.1 KPI (6 cartes)
| Libellé | Valeur | Sous-texte |
|---|---|---|
| **Aujourd'hui** | `1 450 / 1 800 kcal` | jauge + `Reste 350 kcal` |
| **Moyenne 7 j** | `1 720 kcal/j` | `Budget moyen : 1 800 kcal/j` |
| **Protéines aujourd'hui** | `96 / 130 g` | jauge (objectif §15.3) |
| **Répartition du jour** | `P 27 % · G 45 % · L 28 %` | en % des kcal |
| **Jours dans le budget** | `18 / 30 j` | sur la période sélectionnée |
| **Écart moyen au budget** | `80 kcal/j` | vert si négatif |
### 9.2 Graphiques
**G1 — « Calories par jour »** (principal, pleine largeur)
- Type : `bar` + `line`. Axe X jours, axe Y kcal (un seul axe — même unité).
- Série 1 `Apports` : barres, couleur par jour via callback `itemStyle.color` : `semantic-positive` si `kcal ≤ budgetOfDay`, `semantic-negative` sinon (comparaison au budget **du jour**, le budget peut évoluer).
- Série 2 `Budget` : ligne en escalier (`step: 'end'`), pointillés, `#C3C2B7`.
- Tooltip axe : `mar. 12/08 — Apports 1 940 kcal · Budget 1 800 kcal · Écart +140 kcal`. Zoom inside + slider. Légende (2 séries).
**G2 — « Macronutriments par jour »** (pleine largeur)
- Type : `bar` empilées, unité **kcal** (P ×4, G ×4, L ×9 — permet la comparaison visuelle avec G1). Séries : `Protéines` `#199E70`, `Glucides` `#C98500`, `Lipides` `#D55181` (espace 2 px entre segments). Légende. Tooltip axe avec grammes ET kcal : `Protéines 96 g (384 kcal)`. Toggle dans le menu ⋯ : « Afficher en grammes ».
**G3 — « Répartition du jour »** (tiers de largeur)
- Type : `pie` (donut, radius `['55%','80%']`). 3 parts P/G/L, mêmes couleurs que G2. Centre : `1 450 kcal` (graphic text). Labels directs : `Protéines 27 %`. Tooltip item : `Glucides — 652 kcal (45 %) · 163 g`. Suit la date du journal (§9.4).
**G4 — « Répartition par repas »** (deux tiers de largeur)
- Type : `bar` empilées 100 % (`stack` + normalisation), une barre par jour. Séries : `Petit-déjeuner` (slot 1), `Déjeuner` (2), `Dîner` (3), `Collations` (4). Tooltip : kcal réels + %. Légende. Objectif : voir si un repas dérape (grignotage).
**G5 — « Top aliments »** (pleine largeur, 320 px)
- Type : `bar` horizontales, top 10 des aliments par kcal totales sur la période. Axe Y : noms d'aliments (tronqués à 24 caractères + tooltip complet), axe X kcal. Une seule série → couleur unique `chart-2`, pas de légende. Label direct à droite de chaque barre : `12 340 kcal`. Tooltip item : `Pain complet — 12 340 kcal · 28 fois · 441 kcal/fois en moyenne`. Clic sur une barre → filtre le journal sur cet aliment (badge de filtre actif au-dessus du journal).
### 9.3 Bibliothèque d'aliments (panneau secondaire)
Accessible par un bouton « Mes aliments » dans l'en-tête de page : panneau latéral (drawer) listant les aliments mémorisés — `Nom` · `kcal/100 g` (ou /portion) · `P/G/L` · `Utilisé n fois` · actions ✎ 🗑. Recherche en tête. C'est cette bibliothèque qu'interroge l'autocomplete du quick-add (§9.5) ; les aliments importés de Foodvisor y sont ajoutés automatiquement (dédupliqués par nom normalisé).
### 9.4 Journal alimentaire (section « Journal du jour »)
- Navigateur de date : ` mer. 13/08/2026 ` + bouton « Aujourd'hui ». Swipe gauche/droite sur mobile.
- Bandeau du jour : `Total : 1 450 / 1 800 kcal · P 96 g · G 163 g · L 45 g` + jauge.
- 4 groupes repliables : **Petit-déjeuner / Déjeuner / Dîner / Collations**, chacun avec sous-total (`620 kcal`) et bouton « + Ajouter un aliment ».
- Ligne d'entrée : nom · quantité (`150 g`) · kcal · P/G/L (masqués sur mobile, visibles au tap) · actions ✎ / 🗑 / ⧉ (dupliquer vers un autre jour/repas).
- Les entrées importées de Foodvisor portent un badge source `Foodvisor` (tooltip : « Importé le 10/08/2026 »).
### 9.5 Modale « Ajouter un aliment » (quick-add global)
- **Recherche** (focus auto) : autocomplete sur la bibliothèque personnelle (aliments déjà saisis/importés), résultats avec kcal/100 g. Sélection → pré-remplit tout.
- **Nom** (texte, requis) · **Repas** (select : Petit-déjeuner / Déjeuner / Dîner / Collations — défaut selon l'heure : < 11 h petit-déj., 1115 h déjeuner, 1518 h collations, > 18 h dîner) · **Date** (défaut : jour affiché dans le journal).
- **Quantité** + **Unité** (`g` / `ml` / `portion`) — si l'aliment vient de la bibliothèque, kcal et macros se recalculent proportionnellement.
- **Calories (kcal)** (requis) · **Protéines (g)** · **Glucides (g)** · **Lipides (g)** (optionnels).
- Case « Mémoriser cet aliment dans ma bibliothèque » (cochée par défaut pour une saisie manuelle).
- Boutons : « Annuler » / « Ajouter » / « Ajouter et continuer » (garde la modale ouverte, vide la recherche — saisie en rafale d'un repas).
---
## 10. Page — Activité & Sport
**Route** `/activite` · **Titre** « Activité & Sport » · **Période par défaut** : 30 j.
Sources : Health Connect (pas, distance, kcal actives via bridge), FitShow (séances tapis), saisie manuelle. `stepsGoal` configurable (défaut 10 000).
### 10.1 KPI (6 cartes)
| Libellé | Valeur | Sous-texte |
|---|---|---|
| **Pas aujourd'hui** | `7 842 / 10 000` | jauge |
| **Moyenne pas (7 j)** | `9 120 /j` | `▲ +6 % vs 7 j précédents` |
| **Kcal actives aujourd'hui** | `320 kcal` | `Moyenne 7 j : 410 kcal/j` |
| **Distance (période)** | `86,4 km` | `≈ 2,9 km/j` |
| **Séances cette semaine** | `3 séances · 2 h 15` | `Semaine dernière : 4 · 3 h 05` |
| **Objectif atteint** | `21 / 30 j` | jours ≥ objectif de pas sur la période |
### 10.2 Graphiques
**G1 — « Pas par jour »** (principal, pleine largeur)
- Type : `bar` + `line`. Série `Pas` : barres `#199E70` (`chart-3`) ; jours ≥ objectif : opacité 100 %, sinon 55 % (renfort non-couleur : l'axe le montre aussi). Série `Moyenne 7 j` : ligne 2 px blanche à 60 %. `markLine` objectif (« Objectif 10 000 »). Tooltip axe, zoom inside + slider. Légende (2 séries).
**G2 — « Calories actives par jour »** (demi-largeur)
- Type : `bar`, série unique `#D95926` (`chart-2`), pas de légende. `markLine` moyenne de période (label « Moy. 410 »). Tooltip axe.
**G3 — « Entraînement par semaine »** (demi-largeur)
- Type : `bar` empilées par semaine ISO, axe Y **heures** (`1 h 30` au tooltip). Séries = types de séance : `Tapis de course` (1), `Marche` (2), `Vélo` (3), `Renforcement` (4), `Autre` (5, muted). Légende. Tooltip axe : détail par type + total semaine.
**G4 — « Distance cumulée »** (pleine largeur, 240 px)
- Type : `line` en aire, cumul de la distance (pas + séances) depuis le début de la période, `#3987E5` avec dégradé. Label direct sur le dernier point (`86,4 km`). Tooltip axe : `12/08 — cumul 84,1 km (+2,3 km ce jour)`.
### 10.3 Carte « Records » (stat tiles, pas un graphique)
4 tuiles : **Max pas en un jour** `18 452 · 21/06/2026` · **Plus longue séance** `1 h 32 · tapis · 05/07/2026` · **Meilleure distance en séance** `12,4 km` · **Meilleure semaine** `78 500 pas · Sem. 25`. Chaque record est cliquable → surligne le jour dans G1.
### 10.4 Table « Séances »
Colonnes : `Date` · `Type` (badge icône) · `Durée` · `Distance` · `Kcal` · `FC moy.` (si dispo) · `Source` (badge : FitShow / Health Connect / Manuel) · actions ✎ 🗑. Filtres : type, source. Tri par date desc.
### 10.5 Modale « Ajouter une séance »
Champs : **Type** (select : Tapis de course / Marche / Course à pied / Vélo / Renforcement / Natation / Autre) · **Date** (défaut aujourd'hui) · **Heure de début** · **Durée** (champ `hh:mm`, requis) · **Distance (km)** (optionnel) · **Calories (kcal)** (optionnel — placeholder « estimées automatiquement si vide », estimation MET simple par type) · **FC moyenne (bpm)** (optionnel) · **Note**. Boutons « Annuler » / « Enregistrer ».
---
## 11. Page — Balance énergétique
**Route** `/balance` · **Titre** « Balance énergétique » · **Période par défaut** : 30 j.
Définitions (constantes en `src/lib/energy.ts`) :
- `BMR` : Mifflin-St Jeor (`10×kg + 6.25×cm 5×âge + s`, `s = +5` homme / `161` femme), recalculé chaque jour avec le poids de tendance.
- `TDEE = BMR × activityFactor` (§15.1) **ou** `BMR + kcal actives mesurées` si la source d'activité est complète (choix dans Réglages : `tdeeMode: 'factor' | 'measured'`).
- `netBalance(day) = kcalIn TDEE(day)`. **Négatif = déficit = vert.**
- `KCAL_PER_KG = 7700` pour toutes les conversions kcal ↔ kg.
- Un jour sans journal alimentaire est **exclu** des cumuls (et hachuré dans les graphes) plutôt que compté à 0 — règle anti-fausses-données.
### 11.1 KPI (6 cartes)
| Libellé | Valeur | Sous-texte |
|---|---|---|
| **Balance aujourd'hui** | `520 kcal` | `Apports 1 450 · Dépense 1 970` |
| **Déficit moyen (7 j)** | `430 kcal/j` | `Cible : 550 kcal/j` |
| **Cumul (période)** | `12 450 kcal` | `≈ 1,6 kg théoriques` |
| **TDEE estimé** | `2 350 kcal/j` | `BMR 1 780 × 1,32` (ou `BMR + actives mesurées`) |
| **Budget quotidien** | `1 800 kcal` | `TDEE 550` |
| **Réel vs théorique** | `+0,4 kg` | `le réel décroche au-dessus du modèle` (voir G4) |
### 11.2 Graphiques
**G1 — « Entrées vs sorties »** (principal, pleine largeur)
- Type : `bar` **en miroir** sur un seul axe kcal : série `Apports` en valeurs positives (`#D95926`, `chart-2`), série `Dépense énergétique` en valeurs négatives (`#3987E5`, `chart-1`, arrondis vers le bas). Ligne zéro marquée (`axisLine` y=0 en `#383835`).
- Tooltip axe : `mar. 12/08 — Apports 1 940 · Dépense 2 310 · Balance 370 kcal` (balance colorée). Légende (2 séries). Zoom inside + slider. Jours sans journal : barres hachurées (`decal`) + mention tooltip « journal incomplet ».
**G2 — « Balance nette quotidienne »** (demi-largeur)
- Type : `bar`, une série `net = in out`. `visualMap.pieces` : `< 0``semantic-positive` (déficit), `> 0``semantic-negative` (surplus). `markLine` à la cible de déficit (`550`, pointillés). Tooltip : `12/08 — Balance 370 kcal (déficit)`.
**G3 — « Déficit cumulé »** (demi-largeur)
- Type : `line` en aire, cumul de `net` depuis le début de période. Couleur `semantic-positive` si le cumul est négatif (cas normal), l'aire se remplit **sous** zéro. Axe Y kcal ; le tooltip donne la double lecture : `Cumul 12 450 kcal ≈ 1,6 kg` (pas de second axe — l'équivalence kg vit dans le tooltip et le KPI).
**G4 — « Poids théorique vs poids réel »** (pleine largeur)
- Type : `line`, 2 séries, axe Y kg (même unité, un seul axe) :
1. `Poids réel (tendance)``#3987E5`, 2 px ;
2. `Poids théorique``#9085E9` (`chart-7`), pointillés `[6,4]` : `startWeight + cumul(net)/7700`, ancré sur la tendance au 1er jour de la période.
- Légende + labels directs en bout de lignes. Tooltip axe : les deux valeurs + `Écart +0,4 kg`. C'est **le** graphique de vérité du module : si le réel et le théorique divergent durablement, le TDEE est recalibré (voir carte méthode).
### 11.3 Carte « Méthode & calibration »
Carte informative repliable : explication en 3 phrases du modèle (BMR Mifflin-St Jeor, TDEE, 7 700 kcal/kg), et ligne de calibration : « Sur les 30 derniers jours, votre dépense réelle estimée d'après la pesée est de **2 410 kcal/j** (modèle : 2 350). » + bouton « Utiliser cette valeur comme TDEE » (écrit un override dans Réglages).
### 11.4 État dégradé
Cette page exige **pesées + journal alimentaire**. S'il manque l'un des deux sur la période : bandeau jaune « Données incomplètes : X jours sans journal alimentaire sur la période. Les cumuls excluent ces jours. »
---
## 12. Page — Vape
**Route** `/vape` · **Titre** « Vape » · **Période par défaut** : 30 j.
Modèle de coût (constantes dans Réglages §15.4) :
- `costPerMl` = (prix base/ml + prix nicotine/ml au taux cible + prix arôme/ml au dosage) — détail affiché en §12.4.
- `coilCostPerDay` = prix résistance ÷ durée de vie moyenne (jours).
- `vapeCostPerDay(d) = ml(d) × costPerMl + coilCostPerDay`.
- `tobaccoCostPerDay` = (cigarettes/jour avant arrêt ÷ cigarettes/paquet) × prix du paquet — **figé à la config baseline**, avec historique de prix optionnel.
- `savings(d) = Σ depuis quitDate (tobaccoCostPerDay vapeCostPerDay)`.
- `avoidedCigarettes = jours depuis quitDate × cigarettes/jour avant` (compteur temps réel au prorata de la journée).
- Consommation ml/jour : dérivée des **recharges** (une recharge de 10 ml le 12/08 puis une le 15/08 ⇒ ~3,3 ml/j lissés sur l'intervalle) ; une saisie quotidienne directe est aussi possible — les deux modes coexistent, la donnée quotidienne prime.
### 12.1 KPI (8 cartes, 2 rangées)
| Libellé | Valeur | Sous-texte |
|---|---|---|
| **Aujourd'hui** | `3,8 ml` | `Moyenne 7 j : 4,1 ml/j` |
| **Nicotine aujourd'hui** | `11,4 mg` | `3,8 ml × 3,0 mg/ml` |
| **Coût moyen / jour** | `0,62 €/j` | `dont résistances 0,17 €/j` |
| **Économies cumulées** | `1 245,80 €` | `vs 8,50 €/j de tabac` — toujours vert |
| **Cigarettes évitées** | `7 654` | compteur animé (odometer), `≈ 15/j` |
| **Sans tabac depuis** | `511 jours` | `depuis le 15/03/2025` |
| **Résistance actuelle** | `J+9` | `Moyenne : 12 j — à surveiller` (badge jaune si ≥ moyenne 2 j, rouge si > moyenne) |
| **Stock estimé** | `34 ml` | `≈ 8 jours restants` (si suivi de stock activé, sinon carte masquée) |
### 12.2 Graphiques
**G1 — « Consommation d'e-liquide »** (principal, pleine largeur)
- Type : `line` + `scatter`. Série `ml/jour` : points `#9085E9` (`chart-7`) à 45 % ; série `Moyenne mobile 7 j` : ligne 2 px `#9085E9`. `markLine` optionnelle « Objectif » si un objectif de réduction est défini (§15.4). Tooltip axe : `12/08 — 4,2 ml · moyenne 7 j : 4,0 ml`. Zoom inside + slider. Légende (2 séries).
- Les jours de **changement de résistance** apparaissent en `markPoint` discrets (petit pictogramme ⚙ sous l'axe, tooltip « Résistance changée »).
**G2 — « Nicotine absorbée »** (demi-largeur)
- Type : `bar`, série unique `mg/jour` `#D55181` (`chart-5`). `markLine` moyenne de période. Tooltip : `12/08 — 12,6 mg (4,2 ml × 3,0 mg/ml)`. Si le taux a changé dans la période, chaque jour utilise le taux effectif de la recharge courante.
**G3 — « Coût quotidien »** (demi-largeur)
- Type : `line`, série `€/jour` `#C98500` (`chart-4`), moyenne mobile 7 j (le brut en points 45 %). `markLine` de référence : `Tabac : 8,50 €/j` (pointillés `#C3C2B7`) — l'écart visuel entre la ligne et la markLine **est** l'économie quotidienne. Tooltip : `12/08 — vape 0,66 € · tabac évité 8,50 € · gain 7,84 €`.
**G4 — « Économies cumulées »** (pleine largeur, 320 px — le graphique plaisir)
- Type : `line` en aire, cumul `savings` depuis `quitDate`, couleur `#0CA30C` avec dégradé d'aire. Période forcée « Tout » par défaut (surcharge locale possible). Label direct sur le dernier point : `1 245,80 €`. `markPoint` aux paliers franchis : `100 €`, `250 €`, `500 €`, `1 000 €`, `2 000 €`, `5 000 €` (pin avec étiquette). Tooltip axe : `12/08 — 1 238,40 € économisés`. Zoom inside + slider.
**G5 — « Durée de vie des résistances »** (pleine largeur, 260 px)
- Type : `bar`, une barre par résistance **terminée**, axe X = date de changement (catégorie), axe Y = durée en jours. Couleur unique `chart-7` ; la résistance courante (en cours) apparaît en dernière barre hachurée (`decal`) avec sa durée provisoire. `markLine` moyenne (label « Moy. 12 j »). Tooltip item : `Changée le 04/08/2026 — a duré 13 j · ~52 ml vapotés · GT Mesh 0,15 Ω`.
### 12.3 Timeline « Jalons santé » (composant liste, pas ECharts)
Timeline verticale (rail à gauche, points verts = atteint, gris = à venir, le prochain jalon porte une jauge de progression). Jalons depuis `quitDate` (libellés exacts) :
| Échéance | Libellé |
|---|---|
| 20 minutes | « Pouls et tension redescendent » |
| 8 heures | « Le monoxyde de carbone diminue de moitié » |
| 24 heures | « Le CO est éliminé, les poumons évacuent les résidus » |
| 48 heures | « Goût et odorat s'améliorent » |
| 72 heures | « Respirer devient plus facile » |
| 2 semaines | « La circulation sanguine s'améliore » |
| 1 mois | « La peau retrouve son éclat » |
| 3 mois | « Toux et fatigue diminuent, souffle nettement meilleur » |
| 6 mois | « Capacité pulmonaire en nette hausse » |
| 1 an | « Risque d'infarctus réduit de moitié » |
| 5 ans | « Risque d'AVC redevenu comparable à un non-fumeur » |
| 10 ans | « Risque de cancer du poumon réduit de moitié » |
### 12.4 Carte « Modèle de coût » (repliable)
Décomposition affichée : `Base PG/VG 0,08 €/ml + Nicotine 0,11 €/ml + Arôme 0,09 €/ml = 0,28 €/ml` · `Résistance : 3,90 € ÷ 12 j = 0,33 €/j` · `Référence tabac : 15 cig./j × (12,50 € / 20) = 9,38 €/j` (valeurs = exemples). Bouton « Modifier le modèle » → Réglages §15.4.
### 12.5 Modale « Recharge » (quick-add global)
Champs : **Quantité (ml)** — numérique, défaut = contenance de flacon par défaut (§15.4), boutons rapides `10` `30` `50` · **Taux de nicotine (mg/ml)** — défaut = taux courant des Réglages · **Date et heure** — défaut maintenant · **Arôme / recette** (texte optionnel, autocomplete sur les précédents) · **Note**. Boutons « Annuler » / « Enregistrer » → toast « Recharge de 10,0 ml enregistrée ✓ ».
Variante dans la même modale (onglets) : **« Conso du jour »** — saisie directe `ml` pour une date (corrige/remplace l'estimation par recharges).
### 12.6 Bouton « Résistance changée » (action un clic)
Depuis le menu « + Ajouter », la page Vape (bouton dédié dans l'en-tête de G5) ou le tableau de bord : enregistre `coilChange(now)` immédiatement, toast « Résistance changée ✓ — la précédente a duré 13 jours » + lien « Ajouter un détail » (ouvre une mini-modale : Modèle (texte, autocomplete), Valeur (Ω), Prix unitaire (défaut Réglages), Note).
### 12.7 Tables
- **« Historique des recharges »** : `Date` · `Quantité` · `Nicotine` · `Arôme` · `Coût estimé` · actions ✎ 🗑.
- **« Historique des résistances »** : `Posée le` · `Retirée le` · `Durée` · `Conso (ml)` · `Modèle` · `Prix` · actions ✎ 🗑.
---
## 13. Page — Finances
**Route** `/finances` · **Titre** « Finances » · **Période par défaut** : **Ce mois-ci** (le `PeriodSelector` affiche ici en tête : `Ce mois-ci` · `3 mois` · `6 mois` · `1 an` · `Tout` · `Personnalisé`).
La page est organisée en 4 onglets internes (tabs sous la topbar) : **Vue d'ensemble · Transactions · Budgets · Récurrents**. L'URL les reflète (`/finances`, `/finances/transactions`, `/finances/budgets`, `/finances/recurrents`).
### 13.1 KPI (communs, affichés sur « Vue d'ensemble »)
| Libellé | Valeur | Sous-texte |
|---|---|---|
| **Solde total** | `4 812,34 €` | `3 comptes` (au dernier import) |
| **Dépenses du mois** | `1 234,56 €` | `Moyenne 6 mois : 1 480,00 €` |
| **Revenus du mois** | `2 300,00 €` | |
| **Cashflow du mois** | `+1 065,44 €` | vert si > 0, rouge sinon |
| **Budget global** | `82 %` | jauge `1 234,56 € / 1 500,00 €` |
| **À catégoriser** | `14 transactions` | carte cliquable → onglet Transactions filtré `catégorie = aucune` (badge jaune si > 0) |
### 13.2 Onglet « Vue d'ensemble » — graphiques
**G1 — « Évolution du solde total »** (pleine largeur)
- Type : `line` en aire, solde total reconstitué jour par jour (somme des comptes), `#3987E5`. Tooltip axe : `12/08 — 4 812,34 €`. Zoom inside + slider. Sous le graphique, rangée de **tuiles par compte** : nom du compte, solde `1 852,10 €`, date de dernière donnée (badge jaune « Dernier import il y a 32 j » si > 30 j).
**G2 — « Dépenses par catégorie »** (demi-largeur, période courante)
- Type : `pie` donut (radius `['50%','78%']`), top 7 catégories + part `Autres` (`#898781`). Couleur = slot fixe de chaque catégorie (§4.4). Centre : total `1 234,56 €`. Labels directs : `Alimentation 24 %`. Tooltip item : `Alimentation — 296,40 € (24 %) · 18 transactions`. **Clic sur une part → onglet Transactions filtré sur la catégorie + la période.**
**G3 — « Dépenses mensuelles par catégorie »** (demi-largeur)
- Type : `bar` empilées, 12 derniers mois, mêmes couleurs de catégories (top 7 + Autres, espaces 2 px). Légende. Tooltip axe : détail par catégorie + total du mois. Mois courant hachuré (`decal`, incomplet). Clic sur un segment → Transactions filtrées (mois + catégorie).
**G4 — « Cashflow mensuel »** (pleine largeur)
- Type : `bar` groupées + `line`, un seul axe € : `Revenus` (barres `semantic-positive`), `Dépenses` (barres `semantic-negative`, affichées en positif, côte à côte), `Net` (ligne blanche 2 px, peut passer sous zéro). Légende (3). Tooltip axe : `juin 2026 — Revenus 2 300,00 € · Dépenses 1 890,50 € · Net +409,50 €`.
**G5 — « Flux du mois »** (pleine largeur, 380 px)
- Type : `sankey`, orientation gauche → droite : nœuds `Revenus` (par catégorie de revenu : Salaire, Autres revenus) → nœud central `Budget du mois` → catégories de dépenses (top 7 + Autres) → l'écart restant sort vers un nœud `Épargne` (vert) si positif. Liens colorés par catégorie cible à 40 % d'opacité. Tooltip lien : `Budget → Alimentation : 296,40 €`. Nœuds : label + montant. Pas de zoom ; drag des nœuds désactivé (`draggable: false`).
### 13.3 Onglet « Transactions »
**Barre de filtres** (une ligne, wrap mobile) : Recherche texte (libellé) · Compte (multi-select) · Catégorie (multi-select avec « Sans catégorie ») · Type (`Tout / Dépenses / Revenus`) · Montant min/max · période (héritée de la page). Badges de filtres actifs effaçables ; bouton « Réinitialiser ».
**Table** (le cœur du module) — colonnes :
| Colonne | Contenu |
|---|---|
| `Date` | `dd/MM/yyyy` (groupes visuels par mois au scroll : sous-en-tête « août 2026 — 1 234,56 € de dépenses ») |
| `Libellé` | libellé bancaire nettoyé, sous-ligne : libellé brut original en 11 px muted |
| `Compte` | badge court (« Courant », « PayPal ») |
| `Catégorie` | **éditable en ligne** : badge coloré (pastille couleur du slot) cliquable → combobox avec recherche ; `Sans catégorie` = badge jaune pointillé |
| `Montant` | aligné droite, tabular ; dépenses `45,90 €` en `text-ink`, revenus `+2 300,00 €` en `semantic-positive` |
| Actions (hover / menu ⋯) | ✎ Modifier · ⚡ **« Créer une règle »** · 🗑 Supprimer (les transactions importées se suppriment avec confirmation renforcée) |
- Sélection multiple (checkbox) → barre d'actions groupées : « Catégoriser (n) », « Supprimer (n) ».
- Une transaction re-catégorisée à la main affiche une pastille « manuel » (les règles ne l'écrasent plus).
**Modale « Créer une règle »** (ouverte depuis une transaction, pré-remplie) :
- **Si le libellé contient** (texte, pré-rempli avec le mot significatif du libellé, ex. `CARREFOUR`) — select d'opérateur : `contient` / `commence par` / `expression régulière`.
- Conditions optionnelles (ajout par bouton « + Condition ») : **Compte est** (select) · **Montant entre** (min/max) · **Type** (dépense/revenu).
- **Alors catégoriser en** : combobox catégorie (+ « Créer une catégorie… » inline).
- Aperçu en direct : « **12 transactions existantes** correspondent à cette règle » + mini-liste des 5 premières.
- Case « Appliquer aux transactions existantes non catégorisées manuellement » (cochée par défaut).
- Boutons « Annuler » / « Créer la règle ». Toast : « Règle créée ✓ — 12 transactions catégorisées ».
### 13.4 Onglet « Budgets »
- Sélecteur de mois (` août 2026 `).
- Liste de **barres de progression par catégorie** (composant, pas ECharts) : pastille + nom · `296,40 € / 350,00 €` · barre 8 px (vert < 80 %, jaune 80100 %, rouge > 100 % — le dépassement déborde en rouge au-delà de 100 % avec largeur plafonnée et libellé `118 %`) · reste `53,60 €` ou `Dépassé de 22,40 €`.
- En tête : budget global (somme) avec la même barre + **« Rythme »** : `Au 13/08, vous avez dépensé 82 % du budget pour 42 % du mois écoulé` (jauge à double repère : position du jour vs consommation).
- Graphique **G6 — « Budget vs réalisé (6 mois) »** : `bar` groupées par mois : `Budget` (barres `#898781` 40 %) vs `Dépensé` (barres `chart-1`), un axe €. Légende. Tooltip axe.
- Bouton « Modifier les budgets » → Réglages §15.5 (ou édition inline du montant au clic).
### 13.5 Onglet « Récurrents »
- Détection automatique : transactions au libellé similaire, périodicité ~mensuelle/hebdo/annuelle (tolérance ±4 j), ≥ 3 occurrences.
- **Table « Dépenses récurrentes détectées »** : `Libellé` · `Catégorie` · `Fréquence` (`Mensuel`, `Annuel`…) · `Montant moyen` · `Dernière occurrence` · `Prochaine échéance estimée` (badge jaune si dépassée = possible résiliation ou changement) · `Coût annuel` (tri par défaut, desc) · actions : « Confirmer » / « Ignorer » (les ignorés vont dans un panneau repliable).
- KPI d'onglet : **« Total récurrent mensuel »** `184,90 €/mois` · **« Part des dépenses »** `15 %` · **« Coût annuel »** `2 218,80 €`.
- Graphique **G7 — « Échéancier du mois »** : frise horizontale du mois (composant custom léger) avec les échéances positionnées au jour, passées en plein / à venir en contour.
### 13.6 Modale « Ajouter une transaction » (manuelle)
Champs : **Type** (toggle Dépense / Revenu) · **Montant (€)** · **Date** · **Libellé** · **Compte** (select) · **Catégorie** (combobox) · **Note**. Utilisée pour les espèces ; badge source « Manuel ».
---
## 14. Page — Imports
**Route** `/imports` · **Titre** « Imports » · pas de `PeriodSelector`.
### 14.1 Zone d'upload (carte principale)
- **Drag & drop** pleine largeur : pointillés `border`, icône `upload-cloud`, texte : « **Glissez un fichier ici** ou cliquez pour parcourir » · sous-texte : « CSV, OFX, XLSX ou ZIP — 20 Mo max ». Multi-fichiers accepté (file d'attente).
- **Choix du profil de source** (select, avec auto-détection : le parseur tente de reconnaître en-têtes/format et pré-sélectionne, badge « détecté automatiquement ») :
- `Health Connect — export (zip/csv)` — pas, poids, distance, kcal actives, séances
- `Foodvisor — export` — journal alimentaire
- `FitShow — export` — séances tapis
- `Relevé bancaire — CSV générique` (avec étape de mappage)
- `Relevé bancaire — OFX`
- `PayPal — activité (CSV)`
- `Pesées — CSV générique` (date; poids[; note])
- `Vape — CSV générique` (date; ml[; mg/ml])
- Flux en 3 étapes (stepper « 1. Fichier → 2. Vérification → 3. Import ») :
1. **Fichier** : upload + profil (+ pour les profils bancaires : select **Compte de destination**, avec « Créer un compte… » inline).
2. **Vérification** : pour le CSV générique, écran de **mappage de colonnes** (aperçu des 5 premières lignes ; pour chaque colonne cible — Date, Libellé, Montant (ou Débit/Crédit séparés), etc. — un select de colonne source ; format de date détecté ; option « ignorer la première ligne ») ; le mappage est mémorisé par profil+banque. Puis **aperçu** : table des 20 premières lignes normalisées + bandeau : « 254 lignes lues · **12 doublons ignorés** (déjà importés) · 2 lignes en erreur ».
3. **Import** : barre de progression, puis récapitulatif.
- Déduplication : hash par ligne normalisée (source + date + montant + libellé, ou date+valeur pour les mesures) — les ré-imports du même fichier sont sans effet (idempotent).
### 14.2 Table « Historique des imports »
Colonnes : `Date` (`13/08/2026 19:32`) · `Fichier` (nom + taille) · `Profil` (badge) · `Lignes` (`254`) · `Importées` (`240` en vert) · `Doublons` (`12` en muted) · `Erreurs` (`2` — badge rouge cliquable) · `Statut` (`Terminé` vert / `Partiel` jaune / `Échec` rouge / `Annulé` muted) · actions : « Détails », **« Annuler cet import »** (rollback : supprime les lignes créées par cet import, avec confirmation « Cette action supprimera les 240 entrées créées par cet import. »).
### 14.3 Panneau « Erreurs » (détail d'un import)
Ouvert depuis la table : liste des lignes en erreur — `N° de ligne` · `Contenu brut` (mono, tronqué) · `Motif` en français (« Date invalide : "31/02/2026" », « Montant illisible : "12,3O" », « Colonne "Poids" absente »). Bouton « Télécharger le rapport d'erreurs (CSV) ».
### 14.4 Carte « Connecteurs & API »
Rappel : « Votre téléphone peut envoyer les données Health Connect automatiquement via l'application compagnon. » + statut de la dernière synchronisation (`Dernière réception : 13/08/2026 07:12 · 4 types de données`) + bouton « Gérer les clés d'appareil » → Réglages §15.6.
---
## 15. Page — Réglages
**Route** `/reglages` · **Titre** « Réglages » · navigation par onglets verticaux (desktop) / accordéon (mobile) : **Profil · Objectif · Nutrition · Vape · Finances · Appareils & API · Application**. Chaque onglet = cartes de formulaires avec bouton « Enregistrer » par carte (toast de confirmation).
### 15.1 Onglet « Profil » (profil corporel)
- **Prénom / pseudonyme** (affichage) · **Sexe** (radio : Homme / Femme — utilisé pour le BMR) · **Date de naissance** (datepicker) · **Taille (cm)**.
- **Niveau d'activité** (select avec descriptions) : `Sédentaire (×1,2)` · `Légèrement actif (×1,375)` · `Modérément actif (×1,55)` · `Très actif (×1,725)` · `Extrêmement actif (×1,9)`.
- **Mode de calcul du TDEE** (radio) : « Facteur d'activité (recommandé au départ) » / « BMR + calories actives mesurées » / « Valeur manuelle : ___ kcal/j » (renseignée aussi par la calibration §11.3).
- Encart calculé en direct : `BMR : 1 780 kcal · TDEE estimé : 2 350 kcal/j`.
### 15.2 Onglet « Objectif »
- **Poids de départ** (pré-rempli : première pesée) et **date de départ**.
- **Poids cible (kg)** · **Rythme visé** (slider `0,25 — 1,0 kg/semaine`, graduations 0,25 · repère « recommandé : 0,5 ») ;
- Lignes calculées en direct : `Déficit quotidien nécessaire : ≈ 550 kcal/j` · `Date d'atteinte estimée : 24/10/2026` ;
- Alternative : saisir une **date cible** → le rythme et le déficit se recalculent (les trois champs sont liés, le dernier modifié gagne).
- Garde-fou : si le budget résultant < 1 200 kcal/j (femme) / 1 500 (homme) : avertissement jaune « Ce rythme impose un budget très bas. Envisagez un objectif plus progressif. »
### 15.3 Onglet « Nutrition »
- **Budget calorique** (radio) : « Automatique : TDEE déficit (1 800 kcal/j actuellement) » / « Manuel : ___ kcal/j ».
- **Objectifs de macros** : Protéines (`g/kg de poids` — défaut 1,6 — ou g fixes), Glucides / Lipides en % des kcal restantes (deux sliders liés).
- **Repas** : liste réordonnable des repas du journal (défaut : Petit-déjeuner, Déjeuner, Dîner, Collations) — renommage possible.
- **Objectif de pas** (utilisé page Activité) : défaut `10 000`.
### 15.4 Onglet « Vape »
Carte **« Référence tabac (avant l'arrêt) »** :
- **Date d'arrêt du tabac** (datepicker — ancre des économies et jalons) · **Cigarettes par jour** (défaut 15) · **Cigarettes par paquet** (défaut 20) · **Prix du paquet (€)** (défaut 12,50) — ligne calculée : `Coût tabac de référence : 9,38 €/j`.
Carte **« Modèle de coût DIY »** :
- **Base PG/VG** : prix (€) + contenance (ml) → `€/ml` calculé.
- **Booster de nicotine** : prix du flacon (€), contenance (ml, défaut 10), concentration (mg/ml, défaut 20).
- **Taux de nicotine cible (mg/ml)** de la préparation (défaut 3,0) → part booster calculée.
- **Arôme** : prix (€), contenance (ml), **dosage ( %)** (défaut 10 %).
- **Résistances** : prix unitaire (€) (+ prix du pack et nombre, au choix).
- **Contenance de flacon par défaut (ml)** pour le quick-add recharge (défaut 10).
- Encart calculé : `Coût de revient : 0,28 €/ml · avec résistances : ≈ 0,62 €/j au rythme actuel`.
- **Objectif de réduction** (optionnel) : `ml/jour visés` et/ou `taux nicotine visé` → matérialisés en markLine sur G1/G2 de la page Vape.
### 15.5 Onglet « Finances »
Quatre cartes :
1. **Comptes** — table CRUD : `Nom` (« Compte courant ») · `Type` (Courant / Épargne / PayPal / Espèces / Autre) · `Banque` · `Solde initial` + `Date du solde initial` · `Devise` (EUR fixe v1) · actions ✎ 🗑 (suppression bloquée si transactions liées — proposer l'archivage).
2. **Catégories** — liste hiérarchique (2 niveaux max) : pastille couleur (slot proposé automatiquement, modifiable parmi les 8), icône (picker), nom ; catégories par défaut livrées : Alimentation, Logement, Transports, Santé, Loisirs, Abonnements, Restaurants, Shopping, Vape/Tabac, Épargne, Revenus (type revenu), Autres. Drag pour réordonner.
3. **Règles de catégorisation** — table : `Priorité` (drag & drop, la première règle qui matche gagne) · `Condition` (résumé lisible : « Libellé contient "CARREFOUR" ») · `Catégorie` · `Nb de transactions touchées` · actif (switch) · actions ✎ 🗑. Bouton « Tester les règles » : ré-exécute sur les non-catégorisées (aperçu avant application).
4. **Budgets mensuels** — table : catégorie · montant €/mois (input inline) · switch « reporter le non-dépensé » (v2, désactivé) ; ligne de total.
### 15.6 Onglet « Appareils & API »
- Texte d'intro : « Créez une clé pour permettre à l'application compagnon Android (Health Connect) d'envoyer ses données à LifeTrack. »
- **Table des clés** : `Nom` (« Pixel de Julien ») · `Préfixe` (`ltk_a3f4…`) · `Portées` (badges : `health:write`, `weight:write`…) · `Créée le` · `Dernière utilisation` (« il y a 2 h » — vert si < 24 h) · actions : 🗑 « Révoquer » (confirmation).
- **Bouton « Créer une clé »** → modale : Nom, portées (checkboxes), puis écran d'affichage **unique** de la clé complète avec bouton copier + avertissement « Cette clé ne sera plus jamais affichée. »
- Encart développeur repliable : méthode + URL du endpoint d'ingestion (`POST /api/v1/ingest`), en-tête `Authorization: Bearer <clé>`, exemple de payload JSON minimal.
- **Journal de synchronisation** : 20 dernières réceptions (date, appareil, types de données, nb d'enregistrements, statut).
### 15.7 Onglet « Application »
- **Thème** : Sombre (défaut) / Clair / Automatique. · **Langue** : Français (v1). · **Fuseau horaire** : Europe/Paris (info, non modifiable v1). · **Premier jour de la semaine** : Lundi.
- **Accessibilité** : switch « Motifs et textures sur les graphiques » (active les textures ECharts `decal` pour daltonisme/impression).
- **Sauvegarde** : « Exporter toutes mes données (JSON) » · « Exporter (CSV par module, ZIP) ».
- **Zone dangereuse** (bordure rouge) : « Vider un module… » (select module + confirmation par saisie du mot SUPPRIMER) · « Supprimer le compte ».
- **Utilisateurs** (préparation multi-utilisateur) : table des utilisateurs (v1 : le seul admin) + bouton « Inviter » désactivé avec badge « bientôt ».
### 15.8 Assistant de premier lancement (first-run wizard)
Plein écran, 4 étapes, jamais bloquant (« Passer » partout sauf étape 1) :
1. **« Créez votre compte administrateur »** — e-mail, mot de passe (×2, jauge de robustesse).
2. **« Votre profil »** — sexe, naissance, taille, poids actuel, niveau d'activité (§15.1).
3. **« Votre objectif »** — poids cible + rythme (§15.2), affichage immédiat du budget kcal.
4. **« Vos modules »** — 3 cartes activables : Vape (si activée → mini-formulaire baseline tabac §15.4), Finances (créer le premier compte), Import (lien vers `/imports`). Bouton final : « C'est parti → » (vers le tableau de bord).
---
## 16. États vides
Chaque page/section a un `EmptyState` (§5.4) avec ce texte exact :
| Contexte | Titre | Texte | Actions |
|---|---|---|---|
| Tableau de bord (tout vide) | « Bienvenue sur LifeTrack 👋 » | « Configurez votre profil et importez vos premières données pour voir vos tableaux de bord prendre vie. » | « Configurer mon profil » · « Importer des données » |
| Poids | « Aucune pesée pour l'instant » | « Ajoutez votre première pesée ou importez un historique — la tendance et les projections apparaîtront dès 3 pesées. » | « + Ajouter une pesée » · « Importer un CSV » |
| Nutrition (journal du jour) | « Rien dans le journal aujourd'hui » | « Ajoutez votre premier aliment ou importez votre historique Foodvisor depuis la page Imports. » | « + Ajouter un aliment » · « Importer Foodvisor » |
| Activité | « Aucune activité enregistrée » | « Connectez l'application compagnon Health Connect, importez un export FitShow, ou saisissez une séance manuellement. » | « + Ajouter une séance » · « Voir les imports » |
| Balance énergétique | « Il manque des données » | « La balance énergétique a besoin de vos pesées **et** de votre journal alimentaire. Complétez ces deux modules pour débloquer cette page. » | « Aller à Poids » · « Aller à Nutrition » |
| Vape | « Module vape non configuré » | « Renseignez votre consommation de cigarettes avant l'arrêt et votre modèle de coût : LifeTrack calculera vos économies au centime près. » | « Configurer la vape » |
| Vape (configurée, sans données) | « Aucune recharge enregistrée » | « Enregistrez votre première recharge d'e-liquide — deux clics suffisent. » | « + Recharge » |
| Finances | « Aucune transaction » | « Importez un relevé bancaire (CSV ou OFX) ou un export PayPal pour démarrer. La catégorisation automatique fera le tri. » | « Importer un relevé » |
| Finances → Récurrents | « Pas encore de récurrents détectés » | « La détection a besoin d'au moins 3 mois de transactions pour repérer vos abonnements et charges fixes. » | — |
| Imports (historique) | « Aucun import pour l'instant » | « Déposez un fichier ci-dessus : LifeTrack détecte le format et vous montre un aperçu avant d'importer quoi que ce soit. » | — |
| Table filtrée sans résultat | « Aucun résultat » | « Aucune ligne ne correspond à ces filtres. » | « Réinitialiser les filtres » |
| Graphique sans données sur la période | (dans le canvas) « Pas de données sur cette période » | — | « Élargir la période » (passe à `Tout`) |
---
## 17. Accessibilité
- **Palette validée** (script de validation CVD — voir §4.4) ; l'ordre des slots ne doit pas être modifié sans re-validation.
- La couleur n'est jamais seule : légendes systématiques dès 2 séries, signes `+`/``, icônes de tendance, labels directs sélectifs, badges texte sur les statuts.
- **« Voir les données »** sur chaque ChartCard = équivalent tabulaire complet (triable, exportable CSV).
- Option « Motifs et textures » (§15.7) : applique des hachures `decal` à 45°/135° sur les séries des graphiques empilés et miroirs.
- Navigation clavier complète : sidebar et tabs focusables, modales avec focus trap, `Esc` ferme, tables navigables aux flèches.
- Contrastes : textes ≥ 4,5:1 sur `surface` ; les 8 slots de série ≥ 3:1 sur `#1A1A19`.
- `prefers-reduced-motion` : désactive l'animation des graphiques et le compteur odometer (affichage direct).
- Tooltips ECharts doublés d'un `aria-label` descriptif par graphique (« Graphique en ligne : évolution du poids du 15/05 au 13/08, de 85,1 à 82,4 kg »).
---
*Fin de la spécification UX. Toute divergence d'implémentation doit être signalée et arbitrée contre ce document.*