@zevra/ui 0.34.0 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/DECISIONS.md +293 -203
  2. package/README.md +21 -21
  3. package/demo/app.html +6 -6
  4. package/demo/index.html +3 -3
  5. package/dist/actions.d.ts +8 -8
  6. package/dist/actions.d.ts.map +1 -1
  7. package/dist/actions.js +1 -1
  8. package/dist/actions.js.map +1 -1
  9. package/dist/catalogue.d.ts +2 -2
  10. package/dist/catalogue.js +67 -67
  11. package/dist/catalogue.js.map +1 -1
  12. package/dist/content.d.ts +23 -23
  13. package/dist/content.d.ts.map +1 -1
  14. package/dist/content.js +16 -16
  15. package/dist/content.js.map +1 -1
  16. package/dist/css-inline.d.ts +1 -1
  17. package/dist/css-inline.d.ts.map +1 -1
  18. package/dist/css-inline.js +4 -4
  19. package/dist/css-inline.js.map +1 -1
  20. package/dist/data-table.d.ts +1 -1
  21. package/dist/data.d.ts +7 -7
  22. package/dist/data.js +5 -5
  23. package/dist/email-tokens.js +1 -1
  24. package/dist/email.d.ts +6 -6
  25. package/dist/email.js +8 -8
  26. package/dist/email.js.map +1 -1
  27. package/dist/feedback.d.ts +10 -10
  28. package/dist/feedback.d.ts.map +1 -1
  29. package/dist/feedback.js +3 -3
  30. package/dist/forms.d.ts +12 -12
  31. package/dist/forms.js +5 -5
  32. package/dist/glyph-data.d.ts +2 -2
  33. package/dist/glyph-data.js +3 -3
  34. package/dist/glyph-urls.js +1 -1
  35. package/dist/glyphes.d.ts +2 -2
  36. package/dist/glyphes.js +4 -4
  37. package/dist/icon-data.d.ts +1 -1
  38. package/dist/icon-data.js +2 -2
  39. package/dist/icon.d.ts +5 -5
  40. package/dist/icon.js +1 -1
  41. package/dist/index.js +4 -4
  42. package/dist/layout.d.ts +21 -21
  43. package/dist/layout.d.ts.map +1 -1
  44. package/dist/layout.js +15 -15
  45. package/dist/layout.js.map +1 -1
  46. package/dist/navigation.d.ts +16 -16
  47. package/dist/navigation.d.ts.map +1 -1
  48. package/dist/navigation.js +9 -9
  49. package/dist/navigation.js.map +1 -1
  50. package/dist/overlays.d.ts +6 -6
  51. package/dist/overlays.d.ts.map +1 -1
  52. package/dist/overlays.js +5 -5
  53. package/dist/select-menu.d.ts +1 -1
  54. package/dist/select-menu.js +2 -2
  55. package/dist/surfaces.d.ts +8 -8
  56. package/dist/surfaces.d.ts.map +1 -1
  57. package/dist/surfaces.js +6 -6
  58. package/dist/surfaces.js.map +1 -1
  59. package/dist/typography.d.ts +4 -4
  60. package/dist/typography.d.ts.map +1 -1
  61. package/dist/typography.js +3 -3
  62. package/dist/typography.js.map +1 -1
  63. package/icons/index.json +1 -1
  64. package/package.json +2 -2
  65. package/react/actions.tsx +9 -9
  66. package/react/catalogue.tsx +68 -68
  67. package/react/content.tsx +28 -28
  68. package/react/css-inline.ts +4 -4
  69. package/react/data-table.tsx +3 -3
  70. package/react/data.tsx +9 -9
  71. package/react/email-tokens.ts +1 -1
  72. package/react/email.ts +10 -10
  73. package/react/feedback.tsx +12 -12
  74. package/react/forms.tsx +15 -15
  75. package/react/glyph-data.ts +3 -3
  76. package/react/glyph-urls.ts +1 -1
  77. package/react/glyphes.ts +4 -4
  78. package/react/icon-data.ts +2 -2
  79. package/react/icon.tsx +6 -6
  80. package/react/index.ts +4 -4
  81. package/react/layout.tsx +22 -22
  82. package/react/navigation.tsx +18 -18
  83. package/react/overlays.tsx +9 -9
  84. package/react/select-menu.tsx +3 -3
  85. package/react/surfaces.tsx +10 -10
  86. package/react/typography.tsx +6 -6
  87. package/src/base.css +10 -10
  88. package/src/components.css +49 -49
  89. package/src/data.css +41 -41
  90. package/src/feedback.css +55 -55
  91. package/src/fonts.css +2 -2
  92. package/src/forms.css +50 -50
  93. package/src/icons.css +10 -10
  94. package/src/index.css +1 -1
  95. package/src/layout.css +27 -27
  96. package/src/motion.css +16 -16
  97. package/src/motion.js +4 -4
  98. package/src/nav.js +5 -5
  99. package/src/navigation.css +93 -62
  100. package/src/overlays.css +14 -14
  101. package/src/rail.js +6 -6
  102. package/src/surfaces.css +11 -11
  103. package/src/tokens.css +14 -14
  104. package/tokens/index.js +8 -8
package/src/motion.css CHANGED
@@ -1,8 +1,8 @@
1
- /* Zevra UI — mouvement : les neuf animations autorisées
1
+ /* Zevra UI, mouvement : les neuf animations autorisées
2
2
  * Dépend de tokens.css. Relevé dans `maquettes/Motion.html`, qui les nomme
3
3
  * et fixe leurs plages de défilement.
4
4
  *
5
- * NEUF, PAS UNE DE PLUS — plus un DOSAGE. La micro-entrée des blocs
5
+ * NEUF, PAS UNE DE PLUS : plus un DOSAGE. La micro-entrée des blocs
6
6
  * (.zv-anim-rise--doux, arbitrée le 25/08/2026, DECISIONS.md) n'est pas un
7
7
  * dixième mouvement : c'est zv-rise à douze pixels, pour les pages
8
8
  * intérieures où l'entrée pleine de la home lasserait à la longue.
@@ -18,24 +18,24 @@
18
18
  *
19
19
  * Les ENTRÉES se jouent UNE FOIS (arbitrage du 26/08/2026, DECISIONS.md) :
20
20
  * l'ancien pilotage par `animation-timeline: view()` rejouait chaque entrée
21
- * à l'envers dès qu'on remontait — le contenu disparaissait sous le lecteur,
21
+ * à l'envers dès qu'on remontait, le contenu disparaissait sous le lecteur,
22
22
  * et une grille haute n'était jamais « contenue », donc jamais posée. Le
23
23
  * paquet arme désormais les entrées par `html.zv-motion` (posé par
24
24
  * motion.js, importable en `@zevra/ui/motion.js`), et chaque élément se
25
25
  * révèle quand il entre au viewport, puis N'EN BOUGE PLUS. Sans JavaScript,
26
- * rien n'est armé : la page s'affiche complète — même dégradation douce
26
+ * rien n'est armé : la page s'affiche complète, même dégradation douce
27
27
  * qu'avant.
28
28
  *
29
29
  * Un échafaudage de scènes (l'expérience épinglée d'une home) peut vouloir
30
30
  * garder le pilotage au défilement : poser `data-zv-motion-skip` sur la
31
- * scène — motion.js n'y touche pas — et re-brancher les @keyframes du
31
+ * scène, motion.js n'y touche pas, et re-brancher les @keyframes du
32
32
  * paquet sur ses timelines nommées. Seuls la barre de progression
33
33
  * (zv-anim-progress) et le point d'étape (zv-anim-dot-active) restent
34
34
  * scroll-pilotés ici : suivre la lecture est leur raison d'être.
35
35
  *
36
36
  * Les deux boucles (drift, breathe) animent les propriétés `translate` et
37
- * `scale` INDÉPENDANTES, jamais `transform` : les éléments concernés — halo
38
- * centré par translateX(-50%), glyphe en scène — portent déjà un transform
37
+ * `scale` INDÉPENDANTES, jamais `transform` : les éléments concernés (halo
38
+ * centré par translateX(-50%), glyphe en scène) portent déjà un transform
39
39
  * de placement, qu'une animation sur `transform` écraserait.
40
40
  *
41
41
  * Décalage : poser `--zv-delay` en style inline. Le stagger d'un groupe est
@@ -96,7 +96,7 @@
96
96
  }
97
97
 
98
98
  /* Le même mouvement, DOSÉ pour les blocs des pages intérieures : huit
99
- * pixels au lieu de seize — le bloc est posé avant que l'œil ait fini
99
+ * pixels au lieu de seize, le bloc est posé avant que l'œil ait fini
100
100
  * d'arriver, et une visite répétée ne le remarque plus. */
101
101
  @keyframes zv-rise-doux {
102
102
  from {
@@ -113,7 +113,7 @@
113
113
  }
114
114
 
115
115
  /* La signature de la page d'accueil : les glyphes convergent vers le Z
116
- * depuis les quatre coins — la seule entrée latérale du système, latérale
116
+ * depuis les quatre coins, la seule entrée latérale du système, latérale
117
117
  * parce qu'elle converge, pas parce qu'elle glisse. */
118
118
  @keyframes zv-converge-tl {
119
119
  from {
@@ -151,7 +151,7 @@
151
151
  }
152
152
 
153
153
  /* Le filet de spectre se dessine : on anime bien la largeur, et non une
154
- * mise à l'échelle — le dégradé doit se révéler, pas s'étirer. */
154
+ * mise à l'échelle, le dégradé doit se révéler, pas s'étirer. */
155
155
  @keyframes zv-filet {
156
156
  from {
157
157
  width: 0;
@@ -161,7 +161,7 @@
161
161
  /* ─── Le contrat au repos : des définitions inertes ─────────────────────
162
162
  * Chaque classe d'entrée possède ici sa définition PROPRE, volontairement
163
163
  * inerte tant que rien n'est armé : sans motion.js, aucune animation n'est
164
- * attachée et ces propriétés n'ont pas d'effet — la page s'affiche
164
+ * attachée et ces propriétés n'ont pas d'effet, la page s'affiche
165
165
  * complète. C'est aussi le point d'ancrage du dosage : le décalage d'un
166
166
  * groupe se règle en posant --zv-delay par élément (120 ms). */
167
167
 
@@ -197,7 +197,7 @@
197
197
  animation-delay: var(--zv-delay);
198
198
  }
199
199
 
200
- /* Les six vagues : 70 ms d'écart — un délai en secondes retrouve son sens
200
+ /* Les six vagues : 70 ms d'écart, un délai en secondes retrouve son sens
201
201
  * maintenant que l'entrée est temporelle. */
202
202
  .zv-anim-pop--2 { animation-delay: calc(var(--zv-delay) + 0.07s); }
203
203
  .zv-anim-pop--3 { animation-delay: calc(var(--zv-delay) + 0.14s); }
@@ -212,7 +212,7 @@
212
212
  /* ─── L'armement, puis la libération ────────────────────────────────────
213
213
  * `:where(html.zv-motion)` garde la spécificité d'une classe simple : les
214
214
  * définitions inertes ci-dessus (délais, noms, durées) restent lisibles à
215
- * travers l'armement, et `.zv-in` — plus bas dans le fichier — gagne à la
215
+ * travers l'armement, et `.zv-in`, plus bas dans le fichier, gagne à la
216
216
  * cascade sans surenchère. Armé, l'élément attend en PAUSE sur la première
217
217
  * image de son animation (fill both) ; libéré, elle se joue une fois et
218
218
  * s'arrête sur l'état final. Le défilement n'y reviendra jamais. */
@@ -257,7 +257,7 @@
257
257
  animation-name: zv-converge-br;
258
258
  }
259
259
 
260
- /* 6 · Badges, jetons d'entités, éléments de preuve — en cascade. */
260
+ /* 6 · Badges, jetons d'entités, éléments de preuve : en cascade. */
261
261
  :where(html.zv-motion) .zv-anim-pop {
262
262
  animation: zv-pop 0.4s var(--zv-easing) both paused;
263
263
  animation-delay: var(--zv-delay);
@@ -310,7 +310,7 @@
310
310
  }
311
311
 
312
312
  /* 9 · La barre de progression, pilotée par le défilement global de la
313
- * page — la seule à lire `scroll(root)` plutôt que sa propre entrée. */
313
+ * page : la seule à lire `scroll(root)` plutôt que sa propre entrée. */
314
314
  @keyframes zv-progress {
315
315
  from {
316
316
  transform: scaleX(0);
@@ -329,7 +329,7 @@
329
329
 
330
330
  /* ─── Accessibilité ─────────────────────────────────────────────────────
331
331
  * Les neuf classes se neutralisent d'un seul bloc. Une durée courte ne
332
- * suffit PAS pour une animation pilotée par le défilement — elle ignore la
332
+ * suffit PAS pour une animation pilotée par le défilement, elle ignore la
333
333
  * durée : il faut couper la timeline et l'animation elles-mêmes. La page
334
334
  * reste complète et lisible, dans son état final. */
335
335
 
package/src/motion.js CHANGED
@@ -1,10 +1,10 @@
1
- /* Zevra UI — motion.js : la révélation unique des entrées.
1
+ /* Zevra UI, motion.js : la révélation unique des entrées.
2
2
  *
3
3
  * Rôle exact, et rien d'autre :
4
- * 1. poser `zv-motion` sur <html> — c'est cette classe qui ARME les
4
+ * 1. poser `zv-motion` sur <html> : c'est cette classe qui ARME les
5
5
  * entrées de motion.css (sans elle, la page s'affiche complète) ;
6
6
  * 2. poser `zv-in`, UNE fois, sur chaque élément d'entrée quand il
7
- * touche le viewport — l'animation se joue, s'arrête sur l'état
7
+ * touche le viewport, l'animation se joue, s'arrête sur l'état
8
8
  * final, et on n'observe plus l'élément : remonter ne défait rien.
9
9
  *
10
10
  * Un échafaudage de scènes scroll-pilotées (l'expérience épinglée d'une
@@ -37,7 +37,7 @@
37
37
  /* Marge basse positive : l'entrée se déclenche un peu AVANT le
38
38
  * viewport, un saut d'ancre n'atterrit jamais sur un écran vide.
39
39
  * Marge haute généreuse : ce que le lecteur a dépassé en volant est
40
- * déjà posé s'il remonte — remonter ne doit jamais rejouer quoi que
40
+ * déjà posé s'il remonte, remonter ne doit jamais rejouer quoi que
41
41
  * ce soit, ni le faire attendre. */
42
42
  { rootMargin: '80% 0px 12% 0px', threshold: 0.01 }
43
43
  );
package/src/nav.js CHANGED
@@ -1,8 +1,8 @@
1
- /* Zevra UI — nav.js : l'ouverture et la fermeture du tiroir de navigation.
1
+ /* Zevra UI, nav.js : l'ouverture et la fermeture du tiroir de navigation.
2
2
  *
3
3
  * Rôle exact, et rien d'autre : poser et retirer `data-open` sur le tiroir,
4
- * en tenir `aria-expanded` sur le bouton. Le reste — le voile, la glissade,
5
- * le verrouillage du défilement — est dans overlays.css et se joue sur cet
4
+ * en tenir `aria-expanded` sur le bouton. Le reste (le voile, la glissade,
5
+ * le verrouillage du défilement) est dans overlays.css et se joue sur cet
6
6
  * attribut seul.
7
7
  *
8
8
  * Le contrat, en quatre attributs :
@@ -13,7 +13,7 @@
13
13
  * les déclencheurs portent alors la même valeur.
14
14
  *
15
15
  * Échap ferme : le CSS ne sait pas le faire, et un panneau qu'on ne ferme
16
- * qu'en visant un voile est pénible au clavier — et impraticable sans souris.
16
+ * qu'en visant un voile est pénible au clavier, et impraticable sans souris.
17
17
  *
18
18
  * S'importe une fois par page : `import '@zevra/ui/nav.js'`. Idempotent, et
19
19
  * sans effet si aucun tiroir n'est présent. Le module observe le document :
@@ -44,7 +44,7 @@
44
44
  }
45
45
 
46
46
  // Délégation : un seul écouteur pour toute la page. Les liens du tiroir
47
- // sont souvent rendus après coup — les câbler un par un obligerait à
47
+ // sont souvent rendus après coup : les câbler un par un obligerait à
48
48
  // relancer le module à chaque fois.
49
49
  document.addEventListener('click', (e) => {
50
50
  const cible = e.target instanceof Element ? e.target : null
@@ -1,24 +1,24 @@
1
- /* Zevra UI — navigation : le chrome de l'applicatif
1
+ /* Zevra UI (navigation : le chrome de l'applicatif
2
2
  * Colonne latérale, barre supérieure, tiroir, feuille de source citée, menu
3
- * déroulant, fil d'Ariane, pagination, mécanique de défilement — et, depuis
3
+ * déroulant, fil d'Ariane, pagination, mécanique de défilement) et, depuis
4
4
  * le 25/08/2026 (hors relevé, voir DECISIONS.md) : l'onglet souligné, le
5
5
  * popover et la barre d'onglets mobile.
6
6
  *
7
7
  * DÉPEND DE :
8
- * · tokens.css — neutres (--ink, --ink-2, --muted, --label, --line,
8
+ * · tokens.css (neutres (--ink, --ink-2, --muted, --label, --line,
9
9
  * --hairline, --panel, --white), --accent / --accent-soft, échelles
10
10
  * --app-* et --mono-*, mesures --tap et --topbar-h, --shadow-menu ;
11
- * · base.css — reset box-sizing, :focus-visible, style des liens sans
11
+ * · base.css) reset box-sizing, :focus-visible, style des liens sans
12
12
  * classe, et la coupure globale prefers-reduced-motion (ce module ne la
13
13
  * repose donc pas) ;
14
- * · layout.css — l'inventaire des couches d'empilement, dont l'échelle
14
+ * · layout.css) l'inventaire des couches d'empilement, dont l'échelle
15
15
  * ci-dessous est le prolongement ;
16
- * · icons.css — .zv-icon (glyphes tracés) et .zv-icon-btn (bouton d'icône
16
+ * · icons.css) .zv-icon (glyphes tracés) et .zv-icon-btn (bouton d'icône
17
17
  * nu, cible --tap), réemployés ici plutôt que redéfinis.
18
18
  * Ne repose ni le reset, ni le body, ni :root.
19
19
  *
20
20
  * NE REDÉFINIT RIEN de ce que les autres modules possèdent déjà :
21
- * .zv-navlink et .zv-nav__* (nav de vitrine, layout.css) ne sont pas touchés —
21
+ * .zv-navlink et .zv-nav__* (nav de vitrine, layout.css) ne sont pas touchés,
22
22
  * l'entrée applicative s'appelle .zv-sidelink ; le déclencheur du menu est
23
23
  * .zv-select et l'onglet en PUCES réemploie .zv-seg, tous deux dans
24
24
  * forms.css. L'onglet souligné, lui, a sa classe ici depuis l'arbitrage du
@@ -27,7 +27,7 @@
27
27
  * Ce module est un RELEVÉ de l'application Mémoire, en production, pas une
28
28
  * proposition de DA : chaque valeur vient de src/components/app-shell.tsx,
29
29
  * src/components/citation.tsx ou des écrans (chat, répertoire, fiche). Ce qui
30
- * manque à la production — focus, états désactivés, clavier — est signalé par
30
+ * manque à la production (focus, états désactivés, clavier) est signalé par
31
31
  * un ⚠️ à l'endroit exact plutôt que comblé en silence, et ce qui n'a AUCUNE
32
32
  * source est étiqueté HORS RELEVÉ ou DÉRIVÉ, jamais présenté comme relevé.
33
33
  *
@@ -45,7 +45,7 @@
45
45
  * AU-DESSUS, sans jamais se mettre à égalité avec une couche de vitrine : un
46
46
  * voile de tiroir et un header fixe départagés par l'ordre du DOM, ce n'est
47
47
  * pas une échelle. Le relevé donnait voile 40, menu 40, feuille 50, tiroir 60 ;
48
- * seuls les RAPPORTS comptent, et ils sont conservés — l'origine est décalée
48
+ * seuls les RAPPORTS comptent, et ils sont conservés, l'origine est décalée
49
49
  * en bloc pour dégager l'inventaire de vitrine :
50
50
  *
51
51
  * menu 60 · voile 60 · feuille de source 70 · tiroir 80
@@ -57,7 +57,7 @@
57
57
  *
58
58
  * ─── Gabarit attendu ───────────────────────────────────────────────────
59
59
  * Toutes ces pièces supposent une racine applicative en `height: 100dvh;
60
- * overflow: hidden; display: flex` — colonne sous le seuil, rangée au-dessus.
60
+ * overflow: hidden; display: flex` : colonne sous le seuil, rangée au-dessus.
61
61
  * Sans elle, aucune zone défilante ne tient sa hauteur et c'est la page
62
62
  * entière qui défile, ce qui ruine la colonne comme la feuille. Cette racine
63
63
  * relève de la mise en page, pas de ce module (cf. notes).
@@ -126,7 +126,7 @@
126
126
  }
127
127
 
128
128
  /* À partir du deuxième groupe, c'est le blanc au-dessus de l'intitulé qui
129
- * sépare — pas un filet. La colonne en porte déjà deux (tête et pied) ; un
129
+ * sépare : pas un filet. La colonne en porte déjà deux (tête et pied) ; un
130
130
  * troisième trait la transformerait en tableau. */
131
131
  .zv-sidegroup + .zv-sidegroup .zv-sidegroup__title {
132
132
  padding-top: 14px;
@@ -208,11 +208,11 @@
208
208
  /* --topbar-h nomme la HAUTEUR DE LA BARRE, et elle seule : 52px. Le filet
209
209
  * qui la termine est un pixel de plus, et c'est pour cela que la feuille de
210
210
  * source commence à 53 (cf. .zv-cite-panel dans le bloc mobile). Les deux
211
- * valeurs de la production sont justes et se répondent — les unifier sur 52
211
+ * valeurs de la production sont justes et se répondent : les unifier sur 52
212
212
  * ferait remonter la feuille par-dessus le filet.
213
213
  * ⚠️ Nuance à connaître avant d'y toucher : base.css impose box-sizing:
214
214
  * border-box, donc ce `border-bottom` est peint À L'INTÉRIEUR des 52px. Le
215
- * modèle « 52 + 1 » n'est littéral que si le filet sort de la boîte —
215
+ * modèle « 52 + 1 » n'est littéral que si le filet sort de la boîte,
216
216
  * une ligne suffirait (`box-shadow: 0 1px 0 var(--line)` au lieu du
217
217
  * `border-bottom`), mais c'est un écart au relevé : à arbitrer, pas à
218
218
  * décider ici. */
@@ -247,10 +247,10 @@
247
247
  * d'icons.css (cible --tap, sans cadre, couleur --muted) posé dans la barre.
248
248
  * Le balisage est donc `class="zv-icon-btn zv-topbar__burger"`, et seul le
249
249
  * décalage optique appartient à la barre : la marge négative aligne le GLYPHE
250
- * sur le bord de la barre alors que la boîte garde ses 40px de cible tactile —
250
+ * sur le bord de la barre alors que la boîte garde ses 40px de cible tactile,
251
251
  * sans elle, il faut choisir entre l'alignement optique et le doigt.
252
252
  * Le relevé ne donne AUCUN état au hamburger ; le survol d'icons.css peint
253
- * --panel, qui est exactement le fond de cette barre — donc invisible ici.
253
+ * --panel, qui est exactement le fond de cette barre : donc invisible ici.
254
254
  * Seule la couleur du glyphe s'assombrit d'un cran, ce qui vient du composant
255
255
  * partagé et non d'une décision de ce module. */
256
256
  .zv-topbar__burger {
@@ -260,7 +260,7 @@
260
260
  /* ─── Barre d'onglets mobile ────────────────────────────────────────────
261
261
  * HORS RELEVÉ (25/08/2026, voir DECISIONS.md). Mémoire navigue au tiroir ;
262
262
  * une application à trois ou quatre sections navigue plus vite par une
263
- * barre basse. CINQ entrées au maximum — au-delà, c'est le tiroir.
263
+ * barre basse. CINQ entrées au maximum : au-delà, c'est le tiroir.
264
264
  *
265
265
  * N'existe que SOUS le point de rupture : au-dessus, la colonne latérale
266
266
  * est là. Tout le dessin vit donc dans le bloc mobile de fin de fichier ;
@@ -280,7 +280,7 @@
280
280
  * géométries sous un seul nom, c'est la dernière qui charge qui gagne : la
281
281
  * croix bordée du chrome devient .zv-chrome-btn.
282
282
  * Un seul emploi : la croix de fermeture du tiroir et de la feuille de source.
283
- * Angles vifs — le rayon signale l'action, le chrome n'en est pas une. */
283
+ * Angles vifs : le rayon signale l'action, le chrome n'en est pas une. */
284
284
 
285
285
  .zv-chrome-btn {
286
286
  display: flex;
@@ -326,13 +326,13 @@
326
326
  }
327
327
 
328
328
  /* Glyphes relevés, à reproduire tels quels :
329
- * · hamburger — SVG 18×12 (viewBox "0 0 18 12"),
329
+ * · hamburger, SVG 18×12 (viewBox "0 0 18 12"),
330
330
  * <path d="M0 1h18M0 6h18M0 11h18" stroke="currentColor" stroke-width="1.5"/> :
331
331
  * trois traits pleine largeur, entraxe 5px, extrémités franches. Ce n'est
332
332
  * PAS une icône 24×24 d'une bibliothèque, et une icône ronde tirée d'un
333
333
  * jeu tiers casserait la géométrie de la barre. Il ne suit donc pas la
334
334
  * grille de 24 de icons.css : il se pose en SVG nu, pas en .zv-icon.
335
- * · fermeture — le caractère ✕ (U+2715) posé en texte, d'où le
335
+ * · fermeture : le caractère ✕ (U+2715) posé en texte, d'où le
336
336
  * `font-size` ci-dessus qui le dimensionne. */
337
337
 
338
338
  /* ─── Feuille de source citée ───────────────────────────────────────────
@@ -340,8 +340,8 @@
340
340
  * .zv-sheet-section (section de PLANCHE de documentation, avec son __title et
341
341
  * son __body) : côte à côte, .zv-sheet__title et .zv-sheet-section__title
342
342
  * donnent à lire un bloc et son modificateur là où il y a deux objets sans
343
- * rapport. Elle prend le nom de ce qui l'ouvre — un clic sur une citation :
344
- * .zv-cite-panel. Les .zv-cite / .zv-cite-row de data.css sont la CITATION dans le fil de texte ; .zv-cite-panel est la feuille qui l'accueille — deux objets distincts, sans collision, et le nom
343
+ * rapport. Elle prend le nom de ce qui l'ouvre, un clic sur une citation :
344
+ * .zv-cite-panel. Les .zv-cite / .zv-cite-row de data.css sont la CITATION dans le fil de texte ; .zv-cite-panel est la feuille qui l'accueille : deux objets distincts, sans collision, et le nom
345
345
  * ne se confond pas avec .zv-src (la PUCE de source, components.css).
346
346
  *
347
347
  * Colonne au-dessus du seuil, feuille quasi plein écran en dessous. Trois
@@ -454,13 +454,13 @@
454
454
  * opposition à une action) hérite du soulignement par défaut du navigateur,
455
455
  * là où la même entrée en <button> n'a rien : dans un même panneau,
456
456
  * « Mon compte » se soulignait et « Se déconnecter » non. Les deux doivent
457
- * se rendre à l'identique — c'est la BALISE qui change, pas l'apparence. */
457
+ * se rendre à l'identique : c'est la BALISE qui change, pas l'apparence. */
458
458
  a.zv-menu__item,
459
459
  a.zv-menu__item:hover {
460
460
  text-decoration: none;
461
461
  }
462
462
 
463
- /* ⚠️ Option SURVOLÉE AU CLAVIER — ajoutée le 25/08/2026 avec la sélection
463
+ /* ⚠️ Option SURVOLÉE AU CLAVIER : ajoutée le 25/08/2026 avec la sélection
464
464
  * dessinée. Ce n'est ni :hover (la souris n'est pas dessus) ni :focus (le
465
465
  * focus reste sur le déclencheur, c'est aria-activedescendant qui porte
466
466
  * l'annonce). Sans cette classe, parcourir la liste aux flèches ne se voit
@@ -482,7 +482,7 @@ a.zv-menu__item:hover {
482
482
 
483
483
  /* ⚠️ Relevé tel quel : le survol ÉCRASE le fond de l'item courant
484
484
  * (accent-soft → hairline), seul le texte reste accent. Volontaire ou non,
485
- * c'est ce qui tourne en production — signalé plutôt que corrigé en silence. */
485
+ * c'est ce qui tourne en production : signalé plutôt que corrigé en silence. */
486
486
  .zv-menu__item--on:hover,
487
487
  .zv-menu__item[aria-selected="true"]:hover {
488
488
  background: var(--hairline);
@@ -510,13 +510,13 @@ a.zv-menu__item:hover {
510
510
  *
511
511
  * ⚠️ CE BLOC DOIT RESTER APRÈS `.zv-menu`, et donc dans CE fichier. Le
512
512
  * panneau d'une entrée porte les deux classes ; `.zv-navdd__menu` reprend à
513
- * `.zv-menu` son display, sa position et son ancrage, à spécificité égale —
513
+ * `.zv-menu` son display, sa position et son ancrage, à spécificité égale :
514
514
  * c'est l'ORDRE qui tranche. Posé dans layout.css, qu'index.css importe
515
515
  * AVANT navigation.css, il perdait : les trois menus d'une barre
516
516
  * s'affichaient en même temps, ouverts en permanence.
517
517
  *
518
- * Une nav de vitrine qui porte l'architecture d'un site — des familles,
519
- * pas quatre liens — a besoin de déplier. Ouverture au survol ET au focus
518
+ * Une nav de vitrine qui porte l'architecture d'un site (des familles,
519
+ * pas quatre liens) a besoin de déplier. Ouverture au survol ET au focus
520
520
  * (`:focus-within`) : en CSS seul, donc accessible au clavier sans une
521
521
  * ligne de JavaScript et sans état à tenir.
522
522
  *
@@ -528,7 +528,7 @@ a.zv-menu__item:hover {
528
528
  }
529
529
 
530
530
  /* Le bouton porte .zv-navlink : il doit se rendre EXACTEMENT comme un lien
531
- * simple de la barre. Piège à éviter — un `font: inherit`, réflexe habituel
531
+ * simple de la barre. Piège à éviter, un `font: inherit`, réflexe habituel
532
532
  * pour dénuder un bouton, remet la fonte du CONTENEUR (16px/400) là où
533
533
  * .zv-navlink pose 14px/500 : les entrées à menu deviennent plus grosses
534
534
  * que les autres. On ne reprend donc du bouton que ce qui doit disparaître. */
@@ -597,7 +597,7 @@ a.zv-menu__item:hover {
597
597
 
598
598
  /* ─── Popover ───────────────────────────────────────────────────────────
599
599
  * HORS RELEVÉ (25/08/2026, voir DECISIONS.md). Le menu impose ses items ;
600
- * il n'existait aucune surface ancrée LIBRE — aide contextuelle,
600
+ * il n'existait aucune surface ancrée LIBRE : aide contextuelle,
601
601
  * mini-formulaire de filtre. Même ancre que le menu (.zv-select-anchor,
602
602
  * forms.css), même surface (filet --line, ombre --shadow-menu), padding de
603
603
  * panneau au lieu d'items. Fermeture par clic extérieur et Échap : au
@@ -679,7 +679,7 @@ a.zv-menu__item:hover {
679
679
 
680
680
  /* `font: inherit` plutôt qu'une police redite : les trois éléments cliquables
681
681
  * de la pagination sont des <button>, et sans lui le navigateur y impose sa
682
- * police système. Hériter reprend exactement la mono du rang — aucune valeur
682
+ * police système. Hériter reprend exactement la mono du rang : aucune valeur
683
683
  * nouvelle n'entre par cette porte. */
684
684
  .zv-pager__arrow,
685
685
  .zv-pager__rank,
@@ -704,7 +704,7 @@ a.zv-menu__item:hover {
704
704
  }
705
705
 
706
706
  /* Pied de tableau : le compte à gauche, un SEUL élément cliquable pour les
707
- * deux sens à droite — pas de numéros de page, pas de page courante. Le
707
+ * deux sens à droite, pas de numéros de page, pas de page courante. Le
708
708
  * `padding` en raccourci annule le `padding-bottom: 2px` du rang de panneau,
709
709
  * qui n'a pas de sens ici. */
710
710
  .zv-pager--foot {
@@ -718,17 +718,17 @@ a.zv-menu__item:hover {
718
718
  /* ⚠️ TROU ASSUMÉ, et il dépasse le style : il n'existe nulle part de page
719
719
  * courante, de numéros, ni de sens séparés. Une pagination digne de ce nom
720
720
  * (précédent et suivant distincts, désactivables, rang courant) est une
721
- * DÉCISION de conception, pas un relevé — cf. notes. */
721
+ * DÉCISION de conception, pas un relevé : cf. notes. */
722
722
 
723
723
  /* ─── Onglets ───────────────────────────────────────────────────────────
724
724
  * ⚠️ AUCUNE MATIÈRE PREMIÈRE : pas un seul jeu d'onglets dans les écrans
725
725
  * relevés. La rangée de PUCES se construit toujours avec .zv-seg-group et
726
- * .zv-seg (forms.css), qui portent déjà la grammaire d'un état actif —
726
+ * .zv-seg (forms.css), qui portent déjà la grammaire d'un état actif :
727
727
  * c'est le motif des FILTRES, qui restreignent une collection.
728
728
  *
729
729
  * L'onglet SOULIGNÉ, longtemps « à faire », est ARBITRÉ le 25/08/2026
730
- * (DECISIONS.md) : trait actif de 2 px — l'épaisseur des filets dessinés du
731
- * système (filet de spectre, rail d'étapes) — en --accent, la couleur de
730
+ * (DECISIONS.md) : trait actif de 2 px (l'épaisseur des filets dessinés du
731
+ * système (filet de spectre, rail d'étapes)) en --accent, la couleur de
732
732
  * l'action, comme l'entrée de nav courante. PAS le spectre : un quatrième
733
733
  * emploi n'existe pas. C'est le motif des VUES d'un même objet (Aperçu /
734
734
  * Sources) ; pour filtrer, prendre les puces. */
@@ -736,14 +736,14 @@ a.zv-menu__item:hover {
736
736
  .zv-tabs-line {
737
737
  display: flex;
738
738
  gap: 22px;
739
- /* La rangée ne s'enroule pas — des onglets sur deux lignes ne se lisent
739
+ /* La rangée ne s'enroule pas, des onglets sur deux lignes ne se lisent
740
740
  * plus comme des onglets : elle déborde dans son propre cadre, jamais
741
741
  * dans la page. */
742
742
  overflow-x: auto;
743
743
  /* La ligne de la rangée est une ombre INTERNE, pas une bordure : les
744
744
  * onglets (étirés à la hauteur de la rangée) peignent leur trait
745
- * par-dessus. Le montage classique — bordure de rangée + marge basse de
746
- * -1px sur l'onglet — fait déborder l'onglet d'un pixel dans un conteneur
745
+ * par-dessus. Le montage classique, bordure de rangée + marge basse de
746
+ * -1px sur l'onglet, fait déborder l'onglet d'un pixel dans un conteneur
747
747
  * en overflow, et cet unique pixel suffit à faire naître un ascenseur
748
748
  * vertical. */
749
749
  box-shadow: inset 0 -1px 0 var(--line);
@@ -793,13 +793,13 @@ a.zv-menu__item:hover {
793
793
  }
794
794
 
795
795
  /* Au survol de l'onglet actif, le trait reste : la vue où l'on se trouve ne
796
- * s'éteint pas sous le pointeur — même règle que l'entrée de nav courante. */
796
+ * s'éteint pas sous le pointeur, même règle que l'entrée de nav courante. */
797
797
  .zv-tabs-line__tab--on:hover,
798
798
  .zv-tabs-line__tab[aria-selected='true']:hover {
799
799
  color: var(--accent);
800
800
  }
801
801
 
802
- /* Le compte de l'onglet — mêmes mesures que celui de la puce (.zv-seg__count,
802
+ /* Le compte de l'onglet, mêmes mesures que celui de la puce (.zv-seg__count,
803
803
  * forms.css) mais SANS sa marge : ici c'est la gouttière de l'onglet qui
804
804
  * espace, et réemployer la classe de la puce doublait l'écart (6 + 7 px). */
805
805
  .zv-tabs-line__count {
@@ -811,7 +811,7 @@ a.zv-menu__item:hover {
811
811
  /* ─── Défilement ────────────────────────────────────────────────────────
812
812
  * La mécanique, elle, est relevée et livrée : `min-height: 0` ET
813
813
  * `min-width: 0`. Sans ces deux-là, un enfant flex refuse de rétrécir en
814
- * dessous de son contenu — la zone déborde, et c'est la page entière qui part
814
+ * dessous de son contenu : la zone déborde, et c'est la page entière qui part
815
815
  * en défilement horizontal. C'est la faute la plus fréquente de ce gabarit. */
816
816
 
817
817
  .zv-scroll-y {
@@ -822,13 +822,13 @@ a.zv-menu__item:hover {
822
822
  }
823
823
 
824
824
  /* ⚠️ HABILLAGE DES BARRES : trou franc, vérifié deux fois. `grep -rn scrollbar`
825
- * sur tout le source de Mémoire ne renvoie AUCUNE occurrence — les cinq zones
825
+ * sur tout le source de Mémoire ne renvoie AUCUNE occurrence : les cinq zones
826
826
  * défilantes utilisent la barre native du système. Il n'existe donc pas de
827
827
  * valeur relevée pour la largeur, le pouce ou le rail, et ce module n'en
828
828
  * applique aucune par défaut.
829
829
  * L'habillage ci-dessous est OPT-IN et n'emploie que des mots-clés plus un
830
830
  * token existant : aucune dimension inventée. ::-webkit-scrollbar est écarté
831
- * pour deux raisons — il exigerait une largeur en pixels sortie de nulle part,
831
+ * pour deux raisons : il exigerait une largeur en pixels sortie de nulle part,
832
832
  * et il remplace les barres flottantes de macOS/iOS par des barres permanentes
833
833
  * qui mangent la colonne. */
834
834
  .zv-scrollbar {
@@ -886,10 +886,10 @@ a.zv-menu__item:hover {
886
886
  * contre app-shell.tsx:268), en supposant que le filet s'ajoute à la
887
887
  * hauteur. Il ne s'y ajoute pas : box-sizing: border-box le peint À
888
888
  * L'INTÉRIEUR des 52px. Ce pixel n'empile donc rien, il ouvre une couture
889
- * par laquelle le fond de page transparaît — et la feuille portant en
889
+ * par laquelle le fond de page transparaît : et la feuille portant en
890
890
  * plus son propre filet haut, le trait apparaissait doublé.
891
891
  *
892
- * Corrigé ici : la feuille commence à 52, et n'a pas de filet propre —
892
+ * Corrigé ici : la feuille commence à 52, et n'a pas de filet propre,
893
893
  * celui de la barre les sépare déjà. C'est un écart ASSUMÉ au relevé, sur
894
894
  * un artefact et non sur une intention. */
895
895
  .zv-cite-panel {
@@ -926,9 +926,9 @@ a.zv-menu__item:hover {
926
926
  padding-bottom: 12px;
927
927
  }
928
928
 
929
- /* ─── Barre d'onglets mobile — le dessin ──────────────────────────────
929
+ /* ─── Barre d'onglets mobile (le dessin ──────────────────────────────
930
930
  * z-index 50 : le SOMMET des couches de page (l'étage de .zv-layer-top,
931
- * layout.css), et SOUS tous les recouvrements applicatifs (60 et plus) —
931
+ * layout.css), et SOUS tous les recouvrements applicatifs (60 et plus))
932
932
  * le voile doit voiler la barre, le tiroir passer devant. C'est du chrome
933
933
  * permanent, pas un enrobage : il ne monte pas dans leur échelle. */
934
934
  .zv-tabbar {
@@ -1033,7 +1033,7 @@ a.zv-menu__item:hover {
1033
1033
  *
1034
1034
  * ⚠️ CE BLOC AUSSI DOIT RESTER APRÈS `.zv-menu`, pour la même raison que
1035
1035
  * `.zv-navdd` : les deux panneaux portent `.zv-menu` et lui reprennent son
1036
- * display et son ancrage à spécificité égale — c'est l'ORDRE qui tranche.
1036
+ * display et son ancrage à spécificité égale, c'est l'ORDRE qui tranche.
1037
1037
  *
1038
1038
  * Les deux vivent À DROITE de la barre, côte à côte : le lanceur pour passer
1039
1039
  * d'un produit à l'autre, le compte pour savoir qui l'on est. C'est la
@@ -1042,10 +1042,10 @@ a.zv-menu__item:hover {
1042
1042
  *
1043
1043
  * ─── L'ÉTAT ────────────────────────────────────────────────────────────
1044
1044
  * Ouverts au survol ET au focus (`:focus-within`), en CSS seul : la barre
1045
- * reste rendue au serveur, sans une ligne à hydrater — même mécanique que
1045
+ * reste rendue au serveur, sans une ligne à hydrater, même mécanique que
1046
1046
  * `.zv-navdd`. Une app qui préfère l'ouverture au CLIC (la convention des
1047
1047
  * lanceurs) pose `.zv-lanceur--ouvert` / `.zv-compte--ouvert` : la prop
1048
- * `ouvert` du composant les rend. C'est la règle du dépôt — un état se dit
1048
+ * `ouvert` du composant les rend. C'est la règle du dépôt : un état se dit
1049
1049
  * en prop, il ne se peint pas depuis l'app.
1050
1050
  */
1051
1051
 
@@ -1056,7 +1056,7 @@ a.zv-menu__item:hover {
1056
1056
  flex: none;
1057
1057
  }
1058
1058
 
1059
- /* Le déclencheur du lanceur : la cible tactile du paquet, sans cadre — le
1059
+ /* Le déclencheur du lanceur : la cible tactile du paquet, sans cadre, le
1060
1060
  * chrome n'est pas une action. */
1061
1061
  .zv-lanceur__btn {
1062
1062
  display: inline-flex;
@@ -1078,7 +1078,7 @@ a.zv-menu__item:hover {
1078
1078
  }
1079
1079
 
1080
1080
  /* Le déclencheur du compte : la pastille d'initiales, cliquable. Elle garde
1081
- * son carré — le rond ici serait un usage de réseau social, et la pastille
1081
+ * son carré : le rond ici serait un usage de réseau social, et la pastille
1082
1082
  * d'initiales du paquet est carrée depuis le premier jour (data.css). */
1083
1083
  .zv-compte__btn {
1084
1084
  display: inline-flex;
@@ -1120,7 +1120,7 @@ a.zv-menu__item:hover {
1120
1120
 
1121
1121
  /* ⚠️ Le `.zv-menu` intérieur est LUI-MÊME en position absolue (il est fait
1122
1122
  * pour s'ancrer seul sous un déclencheur). Imbriqué dans un panneau qui
1123
- * porte déjà l'ancrage, il se positionnait une SECONDE fois — à partir du
1123
+ * porte déjà l'ancrage, il se positionnait une SECONDE fois : à partir du
1124
1124
  * bord droit du panneau, donc vers la droite, et la grille sortait de
1125
1125
  * l'écran sur téléphone. Sur grand écran la place restante le masquait :
1126
1126
  * c'est le rendu à 390px qui l'a montré. Ici le panneau ancre, le menu ne
@@ -1199,7 +1199,7 @@ a.zv-menu__item:hover {
1199
1199
  padding: 6px 8px 10px;
1200
1200
  }
1201
1201
 
1202
- /* ⚠️ CINQ tuiles — le compte du jour, depuis que le lanceur ne liste que les
1202
+ /* ⚠️ CINQ tuiles : le compte du jour, depuis que le lanceur ne liste que les
1203
1203
  * produits joignables. Dans quatre colonnes, elles donnent 4 + 1 : une
1204
1204
  * dernière rangée d'UNE tuile avec trois cases vides à sa droite, exactement
1205
1205
  * ce que le passage à quatre colonnes avait servi à supprimer pour sept.
@@ -1294,7 +1294,7 @@ a.zv-menu__item:hover {
1294
1294
  }
1295
1295
 
1296
1296
  /* L'accroche : ce que le produit FAIT, en deux mots. C'est elle qui rend la
1297
- * liste lisible à quelqu'un qui ne connaît pas encore la suite — un nom de
1297
+ * liste lisible à quelqu'un qui ne connaît pas encore la suite : un nom de
1298
1298
  * marque seul ne dit rien à un client qui découvre. */
1299
1299
  .zv-lanceur__accroche {
1300
1300
  font-size: var(--mono);
@@ -1303,7 +1303,7 @@ a.zv-menu__item:hover {
1303
1303
  /* PAS de `overflow-wrap: anywhere` ici, contrairement au nom : un nom de
1304
1304
  * marque qu'on coupe reste reconnaissable, une accroche coupée en plein
1305
1305
  * mot ne se lit plus. Si elle ne tient pas, c'est la colonne qui est trop
1306
- * étroite — et c'est la colonne qu'on corrige. */
1306
+ * étroite : et c'est la colonne qu'on corrige. */
1307
1307
  text-wrap: balance;
1308
1308
  }
1309
1309
 
@@ -1314,7 +1314,7 @@ a.zv-menu__item:hover {
1314
1314
 
1315
1315
  /* ─── L'en-tête du panneau de compte ────────────────────────────────────
1316
1316
  * Qui l'on est, avant ce qu'on peut faire. Non connecté, il porte l'invite
1317
- * de connexion : la zone de compte existe TOUJOURS, connecté ou non — une
1317
+ * de connexion : la zone de compte existe TOUJOURS, connecté ou non, une
1318
1318
  * barre où l'on ne peut pas se connecter est une barre où l'on ne revient
1319
1319
  * pas. */
1320
1320
 
@@ -1345,7 +1345,7 @@ a.zv-menu__item:hover {
1345
1345
  }
1346
1346
 
1347
1347
  /* Le pied du panneau : la déconnexion, séparée de ce qui précède. Elle ne
1348
- * prend PAS l'encre d'erreur — se déconnecter n'est pas un incident. */
1348
+ * prend PAS l'encre d'erreur : se déconnecter n'est pas un incident. */
1349
1349
  .zv-compte__sortie {
1350
1350
  border-top: 1px solid var(--hairline);
1351
1351
  margin-top: 4px;
@@ -1359,9 +1359,40 @@ a.zv-menu__item:hover {
1359
1359
  display: none;
1360
1360
  }
1361
1361
 
1362
- /* Les deux panneaux sont plafonnés à l'écran, comme le menu et le
1363
- * popover : au bord droit d'un téléphone, un panneau de 300px pousserait
1364
- * la page en défilement horizontal. */
1362
+ /* ⚠️ ANCRÉS À L'ÉCRAN, plus à leur déclencheur. Hors média ils sont en
1363
+ * `right: 0`, donc calés sur le bord droit du BOUTON, or le lanceur n'est
1364
+ * pas au bord de l'écran : le compte et le burger le suivent. Le panneau
1365
+ * avait donc la bonne largeur et la mauvaise place. Mesuré à 375px sur
1366
+ * mcp-factory : largeur 343, bord gauche à −65 ; deux tuiles sur cinq
1367
+ * coupées, et la partie hors écran inatteignable.
1368
+ *
1369
+ * Et c'est SILENCIEUX : un débordement par la gauche n'engendre aucun
1370
+ * défilement horizontal, le navigateur le rogne sans rien dire.
1371
+ *
1372
+ * COMMENT : le déclencheur cesse d'être le repère (`position: static`), et
1373
+ * le panneau se résout alors contre `.zv-nav`, qui est déjà positionnée et
1374
+ * qui fait la largeur de la barre. `top: 100%` tombe donc sous la barre, et
1375
+ * `left`/`right` la bordent. Largeur pleine, marges de 16 : le motif que
1376
+ * tout le monde connaît sur téléphone, et qui ne dépend plus de ce qui est
1377
+ * posé à droite du déclencheur.
1378
+ *
1379
+ * ⚠️ Poser `left`/`right` SANS neutraliser le `relative` du déclencheur ne
1380
+ * marche pas, je l'ai écrit ainsi d'abord : les offsets se résolvaient
1381
+ * contre le bouton, large de 40px, et le panneau tombait à 8px de large.
1382
+ * C'est scopé à `.zv-nav` : un lanceur posé ailleurs garde son ancrage. */
1383
+ .zv-nav .zv-lanceur,
1384
+ .zv-nav .zv-compte {
1385
+ position: static;
1386
+ }
1387
+
1388
+ .zv-nav .zv-lanceur__panneau,
1389
+ .zv-nav .zv-compte__panneau {
1390
+ left: 16px;
1391
+ right: 16px;
1392
+ }
1393
+
1394
+ /* Le plafond devient redondant pour ces deux-là : il ne coûte rien et
1395
+ * protège un panneau qu'on poserait ailleurs. */
1365
1396
  .zv-lanceur__panneau .zv-menu,
1366
1397
  .zv-compte__panneau .zv-menu {
1367
1398
  max-width: calc(100vw - 32px);
@@ -1370,7 +1401,7 @@ a.zv-menu__item:hover {
1370
1401
  /* DEUX colonnes, et pas trois. Sur un iPhone SE (320px), trois colonnes
1371
1402
  * donnent 63px par tuile : « Anonymisation » s'y coupait en plein mot et
1372
1403
  * « Connecteur » perdait son r sur la ligne suivante. À deux, chaque tuile
1373
- * dispose d'une centaine de pixels — assez pour son accroche, qui est
1404
+ * dispose d'une centaine de pixels : assez pour son accroche, qui est
1374
1405
  * précisément ce dont un client a besoin sur le petit écran où il connaît
1375
1406
  * le moins la suite. Sept produits font alors quatre rangées, et le
1376
1407
  * panneau défile. */