# Kagu — Design system v1

Moteur de recherche de meubles **par dimensions** : l'utilisateur saisit l'emplacement à combler (largeur, hauteur, profondeur max) et une marge ; Kagu ne montre que ce qui rentre à coup sûr. Une photo de la pièce permet d'ajouter des suggestions de styles et de teintes.

- Vitrine du système : [`design-system.html`](design-system.html) (tokens lus en direct, composants interactifs, démonstrations d'animation).
- Application du système : [`index.html`](index.html) (mockup interactif, données fictives).
- Pile technique : HTML, CSS et JavaScript natifs. Aucun build, aucun framework, chemins relatifs, scripts classiques (pas de modules) : le dossier se dépose tel quel sur un hébergement statique, y compris en `file://`.

---

## 1. Idée directrice : « Le vide d'abord »

Les identités du secteur (et les tentatives précédentes) tournent autour de la métaphore du mètre : règle graduée, ruban, cotes de plan. Kagu l'évite volontairement.

Son point de départ est ce que l'utilisateur possède réellement : **un creux à combler**. L'interface raconte deux choses seulement — le **creux** (l'emplacement) et le **plein** (le meuble qui s'y emboîte) — et une seule couleur dit « il reste de la place ».

### Les cinq principes (par ordre de priorité)

1. **Le vide d'abord.** Tout élément se juge à ce qu'il laisse libre. On montre le jeu avant le prix.
2. **Creux et plein.** Ce qu'on saisit ou mesure est *en creux* (champs, niche, plaques, pistes). Ce qu'on touche pour agir est *plein* et à plat (boutons, cartes). Seul ce qui flotte (feuille, toast) porte une ombre.
3. **Un seul signal.** Le citron (`--vide`) signifie « espace libre » et rien d'autre : jamais un bouton, un badge, une promo. Deux apparitions par écran au plus.
4. **Le mouvement explique.** Redimensionner, emboîter, reclasser, scanner : une animation rend un changement d'état lisible, puis s'arrête.
5. **Le pouce d'abord.** Cibles de 44 px, clavier numérique, glisser directement sur la niche, actions en bas d'écran. Le bureau gagne de la place, pas des idées.

### La signature : l'encoche

Un coin évidé (`--notch: 56px`, rayon intérieur `--notch-r: 14px`) qui accueille l'action de la carte (le cœur des favoris). Le logo est construit sur le même geste. Elle remplace bordure, ombre et badge. Implémentation : un élément `.card__cut` de la couleur de fond derrière la carte (`--behind`) et deux pseudo-éléments en dégradé radial pour les raccords arrondis.

---

## 2. Identité

| Élément | Règle |
|---|---|
| Symbole | Carré 32 (rayon 9) dont le coin haut-droit est évidé (14 × 14, rayon 3). Une pièce citron 8 × 8 (rayon 3) flotte dans l'encoche à 3 de chaque paroi. |
| Mot | « kagu », minuscules, Bricolage Grotesque 800, largeur 75 %. |
| Couleurs | Fond clair : carré encre. Fond encre : carré craie. La pièce reste toujours citron. |
| Taille minimale | Symbole 16 px. Zone libre : la hauteur de la pièce (¼ du symbole). |
| Interdits | Recolorer la pièce, fermer l'encoche, capitales, étirer. |
| Favicon | `img/favicon.svg` (même construction). |

---

## 3. Couleurs (OKLCH)

Toutes les valeurs vivent dans `css/tokens.css`. On n'écrit jamais de valeur brute dans les composants.

### Surfaces
| Token | OKLCH | Rôle |
|---|---|---|
| `--paper` | `0.968 0.012 172` | Fond de page (craie verte) |
| `--surface` | `0.994 0.004 172` | Plein : cartes, boutons clairs |
| `--sunk` | `0.928 0.018 176` | Creux : champs, plaques, pistes |
| `--sunk-2` | `0.884 0.022 178` | Creux profond : pistes remplies, survol de plaque |
| `--line` | `0.850 0.020 180` | Filets |

### Encre
| Token | OKLCH | Rôle |
|---|---|---|
| `--ink` | `0.235 0.045 195` | Texte, boutons pleins, scène (nuit sapin) |
| `--ink-2` | `0.300 0.050 195` | Fond du champ de la niche |
| `--ink-3` | `0.380 0.052 195` | Survol des boutons pleins |
| `--ink-deep` | `0.180 0.040 195` | Intérieur de la niche |
| `--on-ink` | `0.975 0.012 172` | Texte sur encre |
| `--on-ink-muted` | `0.800 0.030 185` | Texte secondaire sur encre |
| `--muted` | `0.480 0.035 195` | Texte secondaire sur clair |

### Signal et états
| Token | OKLCH | Rôle |
|---|---|---|
| `--vide` | `0.900 0.190 122` | **Espace libre.** Rien d'autre. Texte posé dessus : `--ink`. |
| `--warn` | `0.820 0.150 78` | Ajustement serré |
| `--alert` / `--alert-soft` | `0.520 0.190 28` / `0.940 0.040 28` | Erreurs |

### Matières (finitions des meubles)
`--m-chene`, `--m-noyer`, `--m-blanc`, `--m-noir`, `--m-sauge`, `--m-terracotta`, `--m-petrole`, `--m-argile`, `--m-moutarde`, `--m-beton`. Le dessin d'un meuble est piloté par une seule variable `--f` ; les ombres et lumières en dérivent par `color-mix()` (`css/art.css`).

### Contrastes (calculés en direct dans `design-system.html`)
Texte courant ≥ 4,5:1 ; grand texte et icônes ≥ 3:1, dans tous les états. Exemples : `--ink` sur `--paper` ≈ 15:1 ; `--muted` sur `--paper` ≈ 5,9:1 ; `--on-ink` sur `--ink-3` ≈ 9:1 ; `--ink` sur `--vide` ≈ 12,6:1.

### Répartition visée
Craie ≈ 62 %, plein ≈ 22 %, encre ≈ 12 %, citron ≤ 4 %.

---

## 4. Typographie

| Rôle | Police | Réglages |
|---|---|---|
| Titres et chiffres | **Bricolage Grotesque** (variable : graisse 200–800, largeur 75–100 %, corps optique) | Titres 650–800, largeur 78–92 % |
| Texte | **Figtree** (variable 300–900) | 16 px, interlignage 1,5, `text-wrap: pretty` |
| Unités et étiquettes | **DM Mono** 400 / 500 | Capitales espacées 0,06 em, chasse fixe |

Polices embarquées en WOFF2 dans `fonts/` (sous-ensemble latin, accents français et œ inclus), avec repli système (`Avenir Next Condensed`, `Arial Narrow`, `system-ui`…). Licence OFL.

### Échelle (fluide, `clamp()`)
| Classe | Variable | Taille | Usage |
|---|---|---|---|
| `.t-display` | `--fs-display` | 44 → 92 px | Titre principal |
| `.t-h1` | `--fs-h1` | 34 → 56 px | Titre de section, nombre de résultats |
| `.t-h2` | `--fs-h2` | 26 → 38 px | Sous-titres |
| `.t-h3` | `--fs-h3` | 19 → 24 px | Titres de bloc |
| `.t-num` | `--fs-num` | 32 → 44 px | Cotes saisies |
| `.t-body` | `--fs-body` | 16 px | Texte |
| `.t-small` | `--fs-small` | 14 px | Texte secondaire |
| `.t-label` | `--fs-micro` | 12 px mono | Étiquettes |

### Français
Signe × entre les cotes (« 140 × 85 cm »), espace fine avant l'unité, espace insécable avant € % : ; ? ! et dans les guillemets « … ». Virgule décimale. Ton concret et chiffré, jamais commercial.

---

## 5. Espaces, formes, relief

- **Espacement** (base 4) : `--s-1` 4 · `--s-2` 8 · `--s-3` 12 · `--s-4` 16 · `--s-5` 24 · `--s-6` 32 · `--s-7` 48 · `--s-8` 64 · `--s-9` 96.
- **Rayons** : `--r-xs` 8 · `--r-s` 12 (boutons, champs) · `--r-m` 18 · `--r-l` 28 (cartes, feuilles) · `--r-pill`.
- **Cibles tactiles** : `--hit` 44 px minimum, `--hit-lg` 52 px pour les boutons principaux.
- **Relief** : plein = aucune ombre ; creux = `--recess` (clair) ou `--recess-dark` (niche) ; flottant = `--lift` (feuilles et toasts uniquement).
- **Gouttière** : `clamp(1rem, 0.5rem + 2.2vw, 2rem)` ; largeur maximale 1480 px.

### Points de rupture
| Largeur | Comportement |
|---|---|
| < 600 | Téléphone : contrôles en colonne, résultats sur 2 colonnes, présélections défilantes, feuilles ancrées en bas (ou plein écran pour la photo), bouton flottant « n meubles » après modification. |
| 600–899 | Grille auto (cartes ≥ 236 px). |
| ≥ 900 | Feuilles centrées sur deux colonnes (visuel + informations). |
| ≥ 1024 | Le panneau « emplacement » devient une colonne fixe de 392 px (432 px dès 1440) qui reste visible pendant le défilement des résultats. |

---

## 6. Composants (`css/components.css`)

| Composant | Classe(s) | Notes |
|---|---|---|
| Boutons | `.btn--primary` / `--secondary` / `--ghost`, `.btn-icon` | Une seule action principale par vue. Au survol, fond **et** texte changent ensemble ; pression : échelle 0,97. Variantes `.on-dark`. |
| Champ de cote | `.dim` | Saisie numérique, boutons ± (maintien = répétition accélérée), interrupteur « Libre ». La taille du chiffre s'adapte à la largeur du champ (container query). |
| Marge | `.range` | Curseur 0–25 cm avec piste remplie ; la butée haute est toujours stricte. |
| Mode | `.seg` | Contrôle segmenté à pastille glissante : « Au plus juste » / « Tout ce qui rentre ». |
| Chips | `.chip` | Filtres à bascule (`aria-pressed`), compteur optionnel. |
| Étiquettes | `.tag` (`--ink`, `--soft`, `--warn`) | Mono capitales. |
| Interrupteur | `.switch` | Sert pour « Libre ». |
| Matière | `.swatch`, `.dot` | Rond 28 px, zone active 44 px, anneau de sélection. |
| Jauges de jeu | `.mini`, `.gauge`, `.fit-row` | Remplissage = part de l'emplacement occupée ; libellé = jeu restant en cm. |
| Carte produit | `.card`, `.plate`, `.card__cut`, `.card__fav` | Plaque en creux (dessin à l'échelle réelle), encoche + cœur, jauges, prix. |
| Niche | `K.NicheView` (`js/niche.js`), `.niche` | Voir §7. Poignées `role="slider"` (flèches, Maj = ±10). |
| Feuille | `<dialog class="sheet">` + `K.sheet` | Natif : piège de focus, Échap, retour du focus. Montée du bas (mobile) / posée au centre (≥ 900). |
| Étapes / analyse | `.steps`, `.shot`, `.ring`, `.palbar` | Parcours photo. |
| Toast, état vide, squelette | `.toast`, `.empty`, `.skel` | L'état vide propose toujours deux issues chiffrées. |

### Règle de correspondance (cœur du produit)

Pour chaque cote active (largeur, hauteur, profondeur) :

- **Butée haute stricte** : `cote du meuble ≤ cote de l'emplacement`. Un meuble plus grand n'est jamais proposé, quelle que soit la marge.
- **Mode « Au plus juste »** : la largeur et la hauteur doivent aussi être ≥ `cote − marge`. La profondeur n'a que la butée haute (« Prof. max »).
- **Mode « Tout ce qui rentre »** : butée haute seule.
- **« Libre »** : la cote est ignorée.
- Les cotes sont **hors-tout** (pieds, plinthes, poignées inclus).
- Une carte par modèle : la taille la mieux ajustée (somme des jeux relatifs la plus faible) ; les autres tailles qui rentrent sont listées dans la fiche.

---

## 7. Mouvement

Une seule famille de courbes, quatre gestes nommés. Les durées sont des tokens ; en `prefers-reduced-motion`, ils tombent à 1 ms et le JavaScript saute ses interpolations.

| Token | Valeur | Usage |
|---|---|---|
| `--d-instant` | 90 ms | Pression, retour tactile |
| `--d-fast` | 160 ms | Survol, couleur, focus |
| `--d-base` | 260 ms | Pastilles, interrupteurs, puces |
| `--d-slow` | 420 ms | Feuilles, jauges, révélations |
| `--d-dock` | 600 ms | Emboîter |
| `--ease-out` | `cubic-bezier(.2,.8,.2,1)` | Arriver, révéler |
| `--ease-dock` | `cubic-bezier(.3,1.35,.5,1)` | Emboîter (léger dépassement) |
| `--ease-inout` | `cubic-bezier(.65,0,.35,1)` | Redimensionner |

| Geste | Ce qu'il explique | Détail | Mouvement réduit |
|---|---|---|---|
| **Respirer** | L'emplacement change de taille | La niche est interpolée en continu (320 ms, `--ease-inout`) ; pas d'interpolation pendant un glisser | Changement instantané |
| **Emboîter** | Un meuble se pose dans le creux | Descente de 16 % avec rebond (560 ms), le citron apparaît une fois posé | Apparition sans descente |
| **Reclasser** | Les résultats ont changé | FLIP : les cartes glissent vers leur nouvelle place (460 ms), les nouvelles entrent en cascade (≤ 10 cartes) | Réordonnancement direct |
| **Scanner** | La photo est analysée | Ligne de balayage (1,5 s, boucle), une pastille par couleur réellement repérée à sa position réelle, étapes cochées une à une | Pas de ligne ; étapes cochées vite |

Autres micro-interactions : nombre de résultats interpolé (380 ms), barres de jeu qui se remplissent, cœur qui rebondit, bump du chiffre à chaque pas, bouton flottant qui monte.

---

## 8. Accessibilité

- Contrastes vérifiés dans tous les états (voir §3) ; couleur jamais seule porteuse d'information.
- Anneau de focus 3 px sur tout élément interactif : encre sur clair, citron (`--focus-ring`) sur encre.
- Zones tactiles de 44 px minimum (zones actives étendues quand le dessin est plus petit).
- Feuilles en `<dialog>` natif ; le nombre de résultats est annoncé (`aria-live`) ; poignées de la niche au clavier.
- Aucune dimension n'est saisissable uniquement au glisser : champ numérique, boutons ± et clavier sont toujours disponibles.
- `prefers-reduced-motion` respecté partout (CSS et JS).

---

## 9. Mise en œuvre

```html
<link rel="stylesheet" href="css/tokens.css">      <!-- couleurs, typo, espaces, mouvement -->
<link rel="stylesheet" href="css/components.css">  <!-- composants -->
<link rel="stylesheet" href="css/art.css">         <!-- habillage des meubles -->
<link rel="stylesheet" href="css/app.css">         <!-- mise en page du mockup -->
```

| Fichier | Rôle |
|---|---|
| `css/tokens.css` | Tokens, polices, base, utilitaires typo |
| `css/components.css` | Composants partagés |
| `css/art.css` | Couleurs dérivées des dessins de meubles |
| `css/app.css` | Mise en page de `index.html` |
| `css/ds.css` | Mise en page de `design-system.html` |
| `js/core.js` | Icônes (sprite), couleur OKLCH, mouvement (`tween`), `sheet`, `toast` |
| `js/art.js` | Moteur de dessin paramétrique : 11 types de meubles, viewBox en centimètres |
| `js/catalogue.js` | Catalogue **fictif** (77 modèles, 413 tailles) |
| `js/niche.js` | Composant niche (redimensionnement, emboîtage, poignées) |
| `js/photo.js` | Parcours photo : palette réelle par k-means en OKLab, styles et teintes |
| `js/app.js` | Recherche, résultats, fiche, favoris |
| `js/ds.js` | Alimente la page du design system |
| `fonts/`, `img/` | Polices WOFF2, favicon, photos d'exemple (crédits dans `img/CREDITS.md`) |

### Ce qui est réel, ce qui est simulé

- **Réel** : la recherche par dimensions et marge, le tri, les filtres, l'adresse partageable (`#l=140&h=85&p=40&m=10`), les favoris (stockage local), le calcul de la palette, de la luminosité, de la chaleur et du contraste sur les pixels de la photo (dans le navigateur, sans envoi).
- **Simulé** : le catalogue (marques, modèles, prix, cotes fictifs), le lien marchand, et le passage de la palette aux styles/teintes, qui repose sur des règles simples et non sur un modèle d'IA.
- Si le navigateur interdit la lecture des pixels (ouverture en `file://` d'une photo d'exemple), des palettes de repli précalculées prennent le relais ; les photos importées par l'utilisateur sont toujours analysées.

---

## 10. Landing (`landing.html`)

Même système, même vocabulaire : le **creux** (un vide dans le mur) et le **plein** (le meuble qui s'y emboîte), le citron réservé à l'espace libre, les composants de `components.css` (cartes à encoche, jauges de jeu, niche, palette). Elle charge `tokens.css`, `components.css`, `art.css`, `app.css` (stage, cartes, palette) puis `landing.css` ; tous ses sélecteurs propres portent le préfixe `l-` pour ne pas heurter `app.css`.

| Fichier | Rôle |
|---|---|
| `landing.html` | Structure : titre d'ouverture, histoire épinglée, sections, appel à l'action |
| `css/landing.css` | Mise en page de la landing, lumière de la pièce dérivée des tokens, mode statique |
| `js/scene.js` | Le salon en SVG (perspective à un point de fuite, 2 unités = 1 cm) ; meubles dessinés par `js/art.js` |
| `js/landing.js` | Timeline GSAP 0 → 100 liée au défilement, démonstrations des sections, révélations |
| `js/vendor/` | `gsap.min.js` et `ScrollTrigger.min.js` 3.15.0 en local, `GSAP-LICENSE.txt` |

### L'histoire (scène épinglée en CSS `sticky`, scrub GSAP)

| Défilement | Ce qui se passe |
|---|---|
| 0 → 14 | Logo, nom et slogan seuls ; ils s'effacent |
| 10 → 36 | La pièce se forme : sol, murs, plafond, fenêtre, rideaux, lumière du jour |
| 36 → 62 | Tapis, canapé, table basse, bibliothèque, plante, cadres, suspension se posent un à un ; la niche apparaît, lueur citron : **le vide** |
| 62 → 84 | La caméra s'approche de la niche, le reste s'assombrit |
| 66 → 92 | La commode Tendre (140 × 82 cm) arrive de devant, glisse, puis s'emboîte dans la niche de 142 × 84 cm |
| 90 → 100 | Le jeu de 2 cm se révèle (citron), anneau d'impact, pastille « Jeu 2 cm », la lumière se rallume |

Règles tenues : seules les **transformations et l'opacité** sont animées ; la scène est épinglée par `position: sticky` (aucun saut de mise en page, CLS mesuré à 0) ; le défilement n'est jamais détourné (pas de `pin` forcé, pas de snap, lien « Passer l'animation ») ; tout est réversible en remontant. Le zoom final et le cadrage sont calculés selon le format (téléphone portrait : cadre resserré sur 1120 unités de large).

### Mouvement réduit

Avec `prefers-reduced-motion: reduce`, la landing affiche la pièce terminée, meuble en place, sans timeline ni ScrollTrigger : le titre, la scène et les quatre légendes se lisent à la suite. Le choix est fait avant le premier rendu (classe `is-static` posée en `<head>`). Sans JavaScript, la mise en page reste la version statique (titre, légendes, sections) : la scène SVG est construite par `js/scene.js`.
