# 001 — HistoMétéo MVP — Spécification Technique v11

> **Itération** : v11 — intègre le feedback Reviewer (`feedback-to-architect-001-v10.md`) + nouvelle demande « Amélioration du responsive et de l'expérience mobile ».

---

## 0) Contract

- **Source of truth** : ce document (`001-histometeo-mvp.tech.v11.md`)
- **Functional integrity** : aucun critère d'acceptation de `001-histometeo-mvp.md` ne peut être modifié, ignoré ou réinterprété.
- **Scope** : fichiers autorisés à modifier :
  - `public/style.css` — réécriture mobile-first des media queries, zones tactiles 44px, suggestions contraintes au viewport, marges réduites sur mobile, onglets navigation élargis, tableaux scroll horizontal explicite
  - `public/index.html` — ajout `<meta name="theme-color">`, éventuels attributs `inputmode` sur les champs
  - `public/app.js` — fermeture suggestions au `touchstart` extérieur, ajustements mineurs d'accessibilité mobile
  - `tests/test_api.py` — aucune modification attendue (pas de changement backend)
- **Forbidden changes** :
  - `src/` — **aucun fichier backend modifié** (zéro changement Python)
  - `src/weather_service.py`, `src/cache.py`, `src/commune_service.py`, `src/config.py`, `src/normals_service.py`, `src/main.py` — aucune modification
  - `docs/` — aucune modification des specs existantes (hors ce fichier)
  - `.github/` — aucune modification
  - `Dockerfile`, `pyproject.toml`, `requirements.txt` — pas de modification
  - `public/assets/` — pas de modification
  - `public/app.js` — aucune nouvelle fonction de rendu, aucune nouvelle section DOM, aucun changement de logique métier. Seuls les ajustements d'interaction mobile (fermeture suggestions, etc.) sont autorisés.
- **Invariants** (tous hérités et préservés) :
  - INV-1 : Aucune donnée utilisateur stockée (ni serveur, ni client)
  - INV-2 : Heures en fuseau `Europe/Paris`
  - INV-3 : Période maximale 31 jours
  - INV-4 : Aucune clé API
  - INV-5 : Interface en français avec accents (`HistoMétéo`)
  - INV-6b : Page unique, URL reflète l'état via des routes propres (path segments)
  - INV-7 : Aucune injection HTML — `textContent`, `createElement`, `replaceChildren()` uniquement, jamais `innerHTML`
  - INV-8 : Le flux recherche simple fonctionne indépendamment du mode comparaison
  - INV-9 : Les normales climatiques sont un enrichissement progressif — leur indisponibilité ne bloque pas l'affichage des données météo ni la navigation
  - INV-10 : Les informations commune sont un enrichissement progressif — leur absence partielle n'empêche pas l'affichage du bloc
  - INV-11 : Le texte contextuel saisonnier et le bloc climat mensuel sont des enrichissements progressifs liés aux normales — leur absence (échec normales) ne dégrade rien
  - **INV-12** (nouveau) : Le comportement responsive est 100% CSS. Aucune détection de user-agent, aucun JS conditionnel basé sur la taille d'écran. Les media queries CSS sont la seule source de vérité pour l'adaptation au viewport.
- **Done when** :
  - Les 11 critères d'acceptation originaux (AC1–AC11) restent vérifiables
  - Toutes les fonctionnalités v2–v10 restent opérationnelles
  - La CSS adopte une structure **mobile-first** : styles de base = mobile, enrichis par `@media (min-width: ...)`
  - Toutes les zones interactives (boutons, onglets, suggestions, liens navigation) ont une hauteur tactile ≥ 44px sur mobile
  - Les suggestions autocomplete sont contraintes à la hauteur du viewport et scrollables
  - Les tableaux larges défilent horizontalement sans casser la mise en page
  - Les graphiques occupent 100% de la largeur disponible sur mobile
  - La barre de navigation résultats (`.results-nav`) reste utilisable sur mobile avec des cibles tactiles suffisantes
  - L'espacement entre éléments interactifs empêche les clics accidentels (≥ 8px gap)
  - Les marges latérales sont réduites sur petit écran pour maximiser la surface utile
  - Le site est testable et utilisable à 360px de large (iPhone SE)
  - La mise en page desktop (≥ 768px) reste visuellement identique à l'actuelle
  - Tous les tests backend existants (61/61) passent sans modification
  - Les recommandations non bloquantes du feedback v10 (R34) sont intégrées

---

## 1) Objectif technique

Transformer la feuille de styles en approche **mobile-first** et améliorer l'ergonomie tactile de l'interface pour offrir une expérience utilisateur fluide sur smartphone, sans modifier le backend ni la logique fonctionnelle existante. Intégrer la correction stylistique R34 du feedback v10.

---

## 2) Analyse du brief

### Besoins principaux

| #   | Besoin                                         | Source         | Complexité | Impact           |
| --- | ---------------------------------------------- | -------------- | ---------- | ---------------- |
| M1  | CSS mobile-first (inversion des media queries) | Demande Mobile | Moyenne    | Architecture CSS |
| M2  | Zones tactiles ≥ 44px                          | Demande Mobile | Faible     | Ergonomie        |
| M3  | Suggestions autocomplete adaptées mobile       | Demande Mobile | Faible     | Ergonomie        |
| M4  | Tableaux à scroll horizontal sur mobile        | Demande Mobile | Faible     | Lisibilité       |
| M5  | Graphiques pleine largeur sur mobile           | Demande Mobile | Faible     | Lisibilité       |
| M6  | Onglets navigation élargis + espacés           | Demande Mobile | Faible     | Navigation       |
| M7  | Marges réduites sur mobile                     | Demande Mobile | Faible     | Espace utile     |
| M8  | Empilement vertical naturel des sections       | Demande Mobile | Faible     | Mise en page     |
| R34 | `let text` → `const text` dans app.js          | Feedback v10   | Trivial    | Qualité code     |

### Contraintes

- **Aucune nouvelle dépendance** — aucune librairie CSS ou JS ajoutée.
- **Aucun changement backend** — le périmètre est exclusivement frontend (CSS principalement, HTML/JS minimalement).
- **La mise en page desktop existante est préservée** — les modifications sont visibles uniquement sous 768px.
- **INV-12** — tout le responsive est piloté par CSS, pas par JS.
- **INV-7 absolu** — aucun `innerHTML` introduit.
- **Les champs date sont déjà `input[type="date"]`** — les navigateurs mobiles affichent nativement un sélecteur calendaire. Aucun composant tiers nécessaire.
- **Les tableaux horaires conservent leur structure `<table>`** — le scroll horizontal est suffisant (déjà amorcé via `.table-wrapper`). La transformation en cartes serait une refonte excessive pour cette itération.

### Risques

| #   | Risque                                                                         | Mitigation                                                                                                                 |
| --- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| 1   | L'inversion mobile-first casse des styles desktop existants                    | Tests visuels desktop avant/après. Les styles desktop existants deviennent les règles dans `@media (min-width: 768px)`.    |
| 2   | Le sticky nav ne fonctionne pas bien sur certains navigateurs mobiles (Safari) | Rendre le nav `position: static` sur mobile (< 640px), comme c'est déjà le cas. Le sticky reste uniquement sur desktop.    |
| 3   | L'augmentation des zones tactiles allonge excessivement le scroll              | Ajustement mesuré : 44px est un minimum, pas une cible disproportionnée. Les marges/paddings sont réduits en compensation. |

---

## 3) Design minimal proposé

### 3.1) Stratégie CSS mobile-first

**Principe** : les styles de base (hors media query) ciblent les écrans mobiles (< 640px). Les enrichissements pour tablette/desktop sont dans `@media (min-width: ...)`.

**Breakpoints retenus** (conservés de l'existant, ordre ajusté) :

| Breakpoint         | Cible          | Usage                                                    |
| ------------------ | -------------- | -------------------------------------------------------- |
| Base (pas de MQ)   | Mobile < 640px | Empilement vertical, padding réduit, zones tactiles 44px |
| `min-width: 640px` | Tablette       | Transitions intermédiaires (nav sticky, espaces élargis) |
| `min-width: 768px` | Desktop        | Grille 2 colonnes dates, padding large, chart plus haut  |

### 3.2) Zones tactiles

Tous les éléments interactifs suivants voient leur `min-height` portée à **44px** dans les styles de base :

| Élément                      | Sélecteur CSS actuel      | Padding actuel   | Padding mobile cible |
| ---------------------------- | ------------------------- | ---------------- | -------------------- |
| Suggestions autocomplete     | `.suggestions li`         | `0.55rem 0.7rem` | `0.75rem 0.8rem`     |
| Boutons presets              | `.preset-buttons button`  | `0.35rem 0.7rem` | `0.55rem 0.85rem`    |
| Bouton recherche             | `#search-button`          | `0.6rem 1.5rem`  | `0.75rem 1.5rem`     |
| Bouton secondaire            | `.btn-secondary`          | `0.5rem 1rem`    | `0.65rem 1rem`       |
| Bouton navigation période    | `.btn-period-link`        | `0.5rem 1rem`    | `0.65rem 1rem`       |
| Onglets navigation résultats | `.results-nav a`          | `0.3rem 0.6rem`  | `0.55rem 0.75rem`    |
| Champs texte/date            | `input[type="text/date"]` | `0.6rem 0.7rem`  | `0.7rem 0.75rem`     |

La `min-height: 44px` est appliquée globalement via une règle partagée :

```css
/* Base mobile — zones tactiles */
.suggestions li,
.preset-buttons button,
#search-button,
.btn-secondary,
.btn-period-link,
.results-nav a,
.btn-cancel {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
```

### 3.3) Suggestions autocomplete

Modifications CSS :

```css
/* Base mobile */
.suggestions {
  max-height: min(220px, 50vh); /* Ne dépasse pas 50% du viewport */
  -webkit-overflow-scrolling: touch; /* Scroll fluide iOS */
}

.suggestions li {
  padding: 0.75rem 0.8rem; /* Zone tactile agrandie */
}
```

Modification JS (app.js) — fermeture des suggestions au toucher extérieur :

```js
document.addEventListener("touchstart", (e) => {
  if (!e.target.closest(".autocomplete")) {
    autocompleteMain.hideSuggestions();
    autocompleteCompare.hideSuggestions();
  }
});
```

> Ce listener existe déjà conceptuellement pour les clics (`mousedown`). Le `touchstart` couvre les appareils tactiles.

### 3.4) Tableaux — scroll horizontal

Déjà en place via `.table-wrapper { overflow-x: auto; }`. Améliorations mobile :

```css
/* Base mobile */
.table-wrapper {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 0 calc(-1 * var(--space-md)); /* Déborde le padding du panel */
  padding: 0 var(--space-md);
}
```

Ajouter un indicateur visuel de scroll (ombre à droite) via pseudo-élément ou gradient :

```css
.table-wrapper {
  background:
    linear-gradient(to right, var(--panel) 30%, transparent),
    linear-gradient(to left, var(--panel) 30%, transparent),
    linear-gradient(to right, rgba(0, 0, 0, 0.08), transparent 15px),
    linear-gradient(to left, rgba(0, 0, 0, 0.08), transparent 15px);
  background-position:
    left center,
    right center,
    left center,
    right center;
  background-size:
    20px 100%,
    20px 100%,
    15px 100%,
    15px 100%;
  background-repeat: no-repeat;
  background-attachment: local, local, scroll, scroll;
}
```

> Cet effet montre une légère ombre sur les bords gauche/droit quand le contenu est scrollable, guidant l'utilisateur.

### 3.5) Graphiques

Déjà `responsive: true` dans Chart.js. Le conteneur CSS est le seul ajustement :

```css
/* Base mobile */
.chart-container {
  height: 250px; /* Un peu plus compact sur mobile */
  width: 100%;
}

/* Desktop */
@media (min-width: 768px) {
  .chart-container {
    height: 400px;
  }
}
```

### 3.6) Navigation résultats (onglets)

```css
/* Base mobile */
.results-nav {
  position: static; /* Pas de sticky sur mobile */
  justify-content: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  padding: 0.5rem;
}

.results-nav a {
  padding: 0.55rem 0.75rem;
  min-height: 44px;
  font-size: 0.9rem;
}

/* Desktop */
@media (min-width: 640px) {
  .results-nav {
    position: sticky;
    top: 0;
    gap: 1.5rem;
    padding: 0.6rem 1rem;
  }

  .results-nav a {
    padding: 0.3rem 0.6rem;
    min-height: unset;
    font-size: 0.95rem;
  }
}
```

### 3.7) Marges et espacements mobiles

```css
/* Base mobile */
body {
  padding-top: var(--space-md); /* Réduit (était --space-lg) */
}

.container {
  padding: 0 var(--space-sm); /* Réduit (était --space-md) */
}

.panel {
  padding: var(--space-md); /* Réduit (était --space-lg) */
}

.info-card {
  padding: var(--space-md); /* Réduit (était --space-lg) */
}

/* Desktop */
@media (min-width: 768px) {
  body {
    padding-top: var(--space-lg);
  }

  .container {
    padding: 0 var(--space-md);
  }

  .panel {
    padding: 1.2rem; /* Conservé de l'existant */
  }

  .info-card {
    padding: var(--space-lg);
  }
}
```

### 3.8) Logo et hero

```css
/* Base mobile */
.logo {
  height: 56px; /* Compact sur mobile (était géré par max-width: 640px) */
}

.hero {
  padding: var(--space-lg) 0 var(--space-sm); /* Réduit */
}

.hero-subtitle {
  font-size: 0.92rem;
}

/* Desktop */
@media (min-width: 640px) {
  .logo {
    height: 80px;
  }

  .hero {
    padding: var(--space-xl) 0 var(--space-md);
  }

  .hero-subtitle {
    font-size: 1rem;
  }
}
```

### 3.9) Day groups (accordéons horaires)

```css
/* Base mobile */
.day-header {
  padding: 0.75rem 0.8rem;
  min-height: 44px;
  gap: 0.5rem;
  flex-wrap: wrap; /* Le résumé passe sous la date si nécessaire */
}

.day-summary {
  font-size: 0.8rem;
}

/* Desktop */
@media (min-width: 640px) {
  .day-header {
    flex-wrap: nowrap;
    gap: 0.8rem;
  }

  .day-summary {
    font-size: 0.9rem;
  }
}
```

### 3.10) Champ de recherche

```css
/* Base mobile */
input[type="text"],
input[type="date"] {
  font-size: 16px; /* ≥ 16px empêche le zoom automatique sur iOS */
  padding: 0.7rem 0.75rem;
  min-height: 44px;
}
```

> **Point critique iOS** : si le `font-size` d'un input est < 16px, Safari zoome automatiquement sur le champ au focus. Fixer à 16px élimine ce comportement parasite sans impact visuel.

### 3.11) Bouton recherche pleine largeur sur mobile

```css
/* Base mobile */
#search-button {
  width: 100%;
  min-height: 44px;
  font-size: 1rem;
}

/* Desktop */
@media (min-width: 768px) {
  #search-button {
    width: auto;
  }
}
```

### 3.12) Correction R34 — `let` → `const` dans app.js

Dans `renderSeoQuestion` et `updateMetaDescription`, remplacer :

```js
// Avant
let text;
text = commune2Name ? ... : ...;

// Après
const text = commune2Name ? ... : ...;
```

### 3.13) HTML — `<meta name="theme-color">`

Ajouter dans `<head>` :

```html
<meta name="theme-color" content="#2a7daf" />
```

> Colore la barre d'adresse mobile (Chrome Android, Safari iOS 15+) avec la couleur accent du site.

---

## 4) Plan d'implémentation

### Étape 1 — Restructuration CSS mobile-first

**Fichier** : `public/style.css`

1. Inverser la logique des media queries : les styles de base deviennent les styles mobiles (ce qui était dans `@media (max-width: 640px)` + les styles sans media query adaptés au mobile).
2. Déplacer les styles desktop dans `@media (min-width: 640px)` et `@media (min-width: 768px)`.
3. Supprimer le bloc `@media (max-width: 640px)` qui n'a plus lieu d'être.
4. S'assurer que les variables CSS (`:root`) restent inchangées.

**Vérifiable** : la page rend correctement à 360px ET à 1024px. Aucun changement visuel sur desktop.

### Étape 2 — Zones tactiles et espacements

**Fichier** : `public/style.css`

1. Ajouter la règle partagée `min-height: 44px` + `display: inline-flex; align-items: center; justify-content: center;` pour les éléments interactifs listés en §3.2.
2. Augmenter les paddings des suggestions (`.suggestions li`), presets (`.preset-buttons button`), inputs, et onglets navigation dans les styles de base.
3. Fixer `font-size: 16px` sur les champs `input[type="text"]` et `input[type="date"]` pour empêcher le zoom iOS.
4. Rendre `#search-button` pleine largeur sur mobile.
5. Réduire les marges/paddings du `.container`, `.panel`, `.info-card`, `body`, `.hero` dans les styles de base, avec restauration dans les breakpoints desktop.

**Vérifiable** : tous les éléments interactifs ont un `min-height` ≥ 44px mesuré via DevTools à 360px. Les inputs ne déclenchent pas de zoom sur iOS.

### Étape 3 — Tableaux et graphiques

**Fichier** : `public/style.css`

1. Ajouter `-webkit-overflow-scrolling: touch` et l'indicateur de scroll (gradient shadows) sur `.table-wrapper`.
2. Réduire la hauteur du `.chart-container` à 250px sur mobile (base), 400px sur desktop.

**Vérifiable** : un tableau de 7+ colonnes est scrollable horizontalement à 360px. Le graphique occupe toute la largeur.

### Étape 4 — Navigation résultats et accordéons

**Fichier** : `public/style.css`

1. Les styles de base de `.results-nav` deviennent `position: static`, `flex-wrap: wrap`, cibles 44px.
2. Le `position: sticky` est rétabli dans `@media (min-width: 640px)`.
3. `.day-header` reçoit `flex-wrap: wrap` et `min-height: 44px` dans les styles de base.

**Vérifiable** : les onglets sont utilisables au touch sans erreur à 360px.

### Étape 5 — Interactions mobile (JS)

**Fichier** : `public/app.js`

1. Ajouter un listener `touchstart` sur `document` pour fermer les suggestions autocomplete quand on touche hors de `.autocomplete`.
2. Corriger R34 : `let text` → `const text` dans `renderSeoQuestion` et `updateMetaDescription`.

**Vérifiable** : les suggestions se ferment en touchant ailleurs sur mobile.

### Étape 6 — HTML meta

**Fichier** : `public/index.html`

1. Ajouter `<meta name="theme-color" content="#2a7daf">` dans `<head>`.

**Vérifiable** : la barre d'adresse est colorée sur Chrome Android.

### Étape 7 — Validation

1. Vérifier visuellement à 360px (Chrome DevTools → iPhone SE) que chaque section est correctement empilée et utilisable.
2. Vérifier visuellement à 1024px que le rendu desktop est identique à l'existant.
3. Exécuter `pytest tests/ -v` — 61/61 tests doivent passer (aucun changement backend).
4. Grep `innerHTML` dans `app.js` → 0 occurrence (INV-7 préservé).

---

## 5) Guide pour le Développeur

### Pièges fréquents

1. **Zoom iOS sur les inputs** : si le `font-size` d'un `<input>` est < 16px, Safari zoome automatiquement. Toujours déclarer `font-size: 16px` (ou `1rem` si le root est à 16px) pour les champs de saisie. Ne pas oublier `input[type="date"]`.

2. **`-webkit-overflow-scrolling: touch`** : propriété dépréciée mais encore nécessaire pour un scroll fluide dans les conteneurs overflow sur iOS < 16. L'ajouter ne cause aucun problème sur les navigateurs modernes.

3. **`min-height: 44px` sur les `<a>` dans `.results-nav`** : les liens inline n'ont pas de hauteur native. Il faut `display: inline-flex; align-items: center;` pour que `min-height` prenne effet.

4. **Gradient d'ombre scroll** : la technique `background-attachment: local, local, scroll, scroll` crée des ombres aux bords qui apparaissent/disparaissent avec le scroll. Elle ne fonctionne que si le `.table-wrapper` a un `background` explicite (pas `transparent`). S'assurer que l'arrière-plan est bien `var(--panel)`.

5. **Ne pas casser l'inline-flex des `.preset-buttons button`** : ils ont déjà un `display: flex` implicite via `flex-wrap`. S'assurer que l'ajout de `inline-flex` pour la zone tactile ne casse pas le layout — utiliser `display: flex` (pas `inline-flex`) pour ces boutons car ils sont enfants d'un conteneur flex.

### Zones de dérive

- **Ne pas introduire de JS conditionnel basé sur `window.innerWidth`** — INV-12 interdit toute adaptation responsive via JS. Tout passe par CSS.
- **Ne pas transformer les tableaux horaires en cartes** — hors périmètre pour cette itération. Le scroll horizontal est la solution retenue.
- **Ne pas ajouter de librairie CSS** (Tailwind, Bootstrap, etc.) — les CSS variables et media queries natives suffisent.
- **Ne pas toucher au Chart.js config** — il est déjà `responsive: true`, le conteneur CSS suffit.

### Simplifications autorisées

- L'ombre scroll sur `.table-wrapper` est un nice-to-have. Si la technique des gradients pose problème (conflits de background avec les panels), elle peut être omise. Le scroll horizontal fonctionne déjà.
- Le `touchstart` listener pour fermer les suggestions peut être combiné avec le listener `mousedown` existant s'il y en a un. Le but est la déduplication, pas la duplication.

### Décisions explicitement interdites

- **Interdit** : ajouter un hamburger menu, un drawer, ou toute navigation mobile complexe. La page est single-page, les onglets suffisent.
- **Interdit** : utiliser `!important` dans les nouvelles règles CSS. Si un conflit de spécificité apparaît, le résoudre par un sélecteur plus précis.
- **Interdit** : modifier la structure DOM des sections résultats. L'ordre est déjà correct pour le mobile.
- **Interdit** : supprimer ou modifier les styles des classes `.anomaly-warm`, `.anomaly-cold`, `.anomaly-wet`, `.anomaly-dry`, `.comparison-winner`.

---

## 6) Stratégie de tests

### Tests automatisés existants

Les 61 tests backend (`pytest tests/ -v`) doivent passer à l'identique. Aucun test n'est ajouté, modifié ou supprimé — il n'y a pas de changement backend.

### Tests manuels requis

| #     | Scénario                                              | Device simulé     | Résultat attendu                                                                                            |
| ----- | ----------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------- |
| TM-1  | Ouvrir la page d'accueil                              | 360px (iPhone SE) | Logo compact, formulaire empilé, aucun défilement horizontal sur le body                                    |
| TM-2  | Rechercher une commune et sélectionner une suggestion | 360px             | Suggestions visibles, hauteur ≥ 44px, scrollable si >5, se ferme au touch extérieur                         |
| TM-3  | Sélectionner des dates via les champs natifs          | 360px             | Le sélecteur natif du navigateur s'ouvre, pas de zoom sur le champ (iOS)                                    |
| TM-4  | Cliquer « Rechercher »                                | 360px             | Bouton pleine largeur, résultats empilés verticalement                                                      |
| TM-5  | Naviguer les onglets Résumé/Graphique/Détail          | 360px             | Onglets tous visibles, cibles tactiles suffisantes, onglet actif visible                                    |
| TM-6  | Scroller un tableau horaire horizontalement           | 360px             | Le scroll est fluide, une ombre indique le contenu masqué                                                   |
| TM-7  | Visualiser le graphique                               | 360px             | Le graphique occupe toute la largeur, hauteur adaptée, lisible                                              |
| TM-8  | Vérifier le rendu desktop                             | 1024px            | Identique visuellement à l'état pré-v11 (grille dates 2 colonnes, paddings larges, nav sticky, chart 400px) |
| TM-9  | Navigation des périodes (← →)                         | 360px             | Boutons empilés verticalement, cibles tactiles 44px                                                         |
| TM-10 | Mode comparaison                                      | 360px             | Tous les panneaux s'empilent, les tableaux scrollent, pas de débordement                                    |

### Edge cases critiques

- **iPhone SE (320px de large en mode portrait)** : le formulaire et les boutons ne doivent pas déborder.
- **Orientation paysage sur mobile** : les onglets de navigation ne doivent pas se superposer.
- **Très longue liste de suggestions** (ex: « saint ») : la liste est bornée par `max-height: min(220px, 50vh)` et scrollable.

---

## 7) Risques techniques

| #   | Risque                                                                | Probabilité | Impact | Mitigation                                                                                                   |
| --- | --------------------------------------------------------------------- | ----------- | ------ | ------------------------------------------------------------------------------------------------------------ |
| 1   | Régression visuelle desktop lors de l'inversion mobile-first          | Moyenne     | Élevé  | Comparer pixel par pixel à 1024px avant/après. Tous les styles desktop actuels sont explicitement conservés. |
| 2   | Le gradient d'ombre scroll ne fonctionne pas sur tous les navigateurs | Faible      | Faible | C'est un indicateur visuel cosmétique, pas fonctionnel. Le scroll fonctionne sans.                           |
| 3   | `min-height: 44px` sur les boutons casse un layout flex existant      | Faible      | Moyen  | Tester chaque groupe de boutons (presets, period-links, results-nav) individuellement à 360px.               |
